在TypeScript开发中,对象类型的定义方式看似简单却暗藏玄机。新手开发者常常对object、Object、{}以及Record<string, any>这些类型声明方式感到困惑。这些类型虽然都能表示"对象"概念,但在类型检查和行为表现上存在关键差异。
object是TypeScript 2.2引入的类型,表示非原始类型的对象。它排除了JavaScript的7种原始类型:string、number、bigint、boolean、symbol、null和undefined。
typescript复制let obj: object;
obj = { name: 'Alice' }; // 正确
obj = [1, 2, 3]; // 正确
obj = () => {}; // 正确
obj = 42; // 错误 - 原始类型
典型应用场景:
Object(大写的O)和{}(空对象字面量类型)在行为上几乎相同,它们都允许任何非null/undefined的值:
typescript复制let obj1: Object;
let obj2: {};
obj1 = 'string'; // 正确
obj2 = 123; // 正确
obj1 = null; // 错误
关键区别:
Object是JavaScript内置对象的接口{}是纯粹的类型表示,不包含任何属性声明从严格到宽松的排序:
object (最严格){} / Objectany (最宽松)优先使用interface:
typescript复制interface User {
name: string;
age: number;
}
需要宽松对象类型时:
Record<string, unknown>替代{}unknown替代any避免混用模式:
typescript复制// 不推荐
function process(data: object | string) {
// 需要类型判断
}
// 推荐
function process(data: unknown) {
if (typeof data === 'object' && data !== null) {
// 安全处理
}
}
当处理未知对象时,类型守卫能提供更好的安全性:
typescript复制function isUser(obj: unknown): obj is User {
return typeof obj === 'object'
&& obj !== null
&& 'name' in obj
&& 'age' in obj;
}
对于动态属性对象,使用索引签名:
typescript复制interface DynamicObject {
[key: string]: number | string;
}
const config: DynamicObject = {
timeout: 1000,
name: 'default'
};
typescript复制// 错误示例
const user: object = { name: 'Bob' };
console.log(user.name); // 错误:Property 'name' does not exist on type 'object'
// 解决方案
interface User {
name: string;
}
const user: User = { name: 'Bob' };
typescript复制// 危险操作
const empty: {} = { name: 'Alice' }; // 不报错但失去类型安全
// 安全替代
const safeEmpty: Record<string, never> = {}; // 真正空对象
类型复杂度影响:
object类型检查最快{}/Object需要额外检查原始值声明合并问题:
typescript复制interface Object {
customMethod(): void; // 影响全局
}
泛型约束选择:
typescript复制function process<T extends object>(obj: T) {
// 比使用any更安全
}
typescript复制function safeParse(json: string): unknown {
return JSON.parse(json);
}
const data = safeParse('{"name":"Alice"}');
if (isUser(data)) {
// 类型安全访问
}
typescript复制type NonNullableObject<T> = T extends object ? T : never;
function ensureObject<T>(input: T): NonNullableObject<T> {
if (typeof input === 'object' && input !== null) {
return input;
}
throw new Error('Not an object');
}
ESLint规则配置:
json复制{
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"Object": "Use 'object' instead",
"{}": "Use 'Record<string, unknown>' instead"
}
}
]
}
团队规范制定:
Record<string, unknown>Object和{}类型文档注释标准:
typescript复制/**
* @typedef {Object} User
* @property {string} name - 用户名
* @property {number} age - 用户年龄
*/
TypeScript采用结构化类型系统(鸭子类型),关注形状而非声明:
typescript复制interface Point {
x: number;
y: number;
}
const obj = { x: 1, y: 2, z: 3 };
const point: Point = obj; // 兼容
当直接使用对象字面量时会进行额外检查:
typescript复制interface Point { x: number; y: number; }
function printPoint(p: Point) {}
printPoint({ x: 1, y: 2, z: 3 }); // 错误
const obj = { x: 1, y: 2, z: 3 };
printPoint(obj); // 正确
typescript复制type PartialUser = Partial<User>; // 所有属性可选
type ReadonlyUser = Readonly<User>; // 只读属性
type UserNames = Pick<User, 'name'>; // 选择属性
typescript复制type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
type Nullable<T> = { [P in keyof T]: T[P] | null };
typescript复制declare function fn1(param: {}): void;
declare function fn2(param: object): void;
fn1('string'); // 正确
fn2('string'); // 错误
typescript复制class MyClass {}
const obj: object = new MyClass(); // 正确
const obj2: MyClass = {}; // 错误
typescript复制declare global {
interface Object {
toJSON<T>(this: T): string;
}
}
typescript复制declare module 'my-module' {
export interface Config {
[key: string]: unknown;
}
}
typescript复制function createMock<T>(override?: Partial<T>): T {
return {
...defaultValues,
...override,
} as T;
}
typescript复制function assertIsObject(value: unknown): asserts value is object {
if (typeof value !== 'object' || value === null) {
throw new Error('Not an object');
}
}
typescript复制interface Config {
size?: number;
color?: string;
}
function createElement(config: Config) {
const defaults: Required<Config> = {
size: 10,
color: 'black'
};
return { ...defaults, ...config };
}
typescript复制function validateConfig(config: unknown): config is Config {
return typeof config === 'object'
&& config !== null
&& ('size' in config ? typeof config.size === 'number' : true)
&& ('color' in config ? typeof config.color === 'string' : true);
}
避免深层嵌套类型:
typescript复制// 不推荐
type DeepObject = {
level1: {
level2: {
level3: string;
};
};
};
// 推荐
interface Level3 {
value: string;
}
interface Level2 {
level3: Level3;
}
interface Level1 {
level2: Level2;
}
使用类型别名缓存:
typescript复制type ComplexType = /* 复杂类型定义 */;
function process1(input: ComplexType) {}
function process2(input: ComplexType) {}
typescript复制function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] | undefined {
return obj[key];
}
typescript复制function mergeObjects<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
typescript复制interface Product {
operation(): string;
}
function createProduct(config: object): Product {
if ('type' in config) {
return new ConcreteProduct();
}
throw new Error('Invalid config');
}
typescript复制interface Strategy {
execute(data: object): void;
}
class Context {
constructor(private strategy: Strategy) {}
setStrategy(strategy: Strategy) {
this.strategy = strategy;
}
execute(data: object) {
this.strategy.execute(data);
}
}
typescript复制async function fetchData(): Promise<object> {
const response = await fetch('/api');
return response.json(); // 返回object类型不安全
}
// 改进方案
async function fetchData<T>(): Promise<T> {
const response = await fetch('/api');
return response.json() as Promise<T>;
}
typescript复制async function safeFetch() {
try {
const data = await fetchData<User>();
// 类型安全访问data
} catch (err) {
if (err instanceof Error) {
// 类型安全处理
}
}
}
typescript复制const user = {
name: 'Alice',
age: 30
}; // 自动推导为 { name: string; age: number; }
function updateUser(user: typeof user) {
// 参数自动获得正确类型
}
typescript复制const config = {
theme: 'dark',
layout: 'grid'
} as const; // 类型为 { readonly theme: "dark"; readonly layout: "grid"; }
代码组织原则:
types目录import type语法减少运行时影响文档生成:
typescript复制/**
* 用户基本信息
* @property {string} name - 用户全名
* @property {number} age - 用户年龄
*/
export interface User {
name: string;
age: number;
}
渐进式类型严格:
@ts-expect-error标记需要后续修复的代码在实际TypeScript项目中,理解这些对象类型的细微差别能显著提高代码质量和开发效率。我个人的经验是:始终优先使用最精确的类型定义,只有在确实需要宽松类型时才考虑使用object或Record,而应该尽量避免使用Object和{}这种过于宽泛的类型声明。