TS 中 Object, object, {} 类型之间的区别

TypeScript 2.2 引入了被称为 object 类型的新类型,它用于表示非原始类型。在 JavaScript 中以下类型被视为原始类型:stringbooleannumberbigintsymbolnullundefined

所有其他类型均被视为非基本类型。新的 object 类型表示如下:

// All primitive types

type Primitive = string

| boolean | number

| bigint | symbol

| null | undefined;

// All non-primitive types

type NonPrimitive = object;

|

让我们看看 object 类型,如何让我们编写更精确的类型声明。

一、使用 object 类型进行类型声明

随着 TypeScript 2.2 的发布,标准库的类型声明已经更新,以使用新的对象类型。例如,Object.create()Object.setPrototypeOf() 方法,现在需要为它们的原型参数指定 object | null 类型:

|

// node_modules``/typescript/lib/lib``.es5.d.ts

interface ObjectConstructor {

create(o: object | null): any;

setPrototypeOf(o: any, proto: object | null): any;

// ...

}

|

将原始类型作为原型传递给 Object.setPrototypeOf()Object.create() 将导致在运行时抛出类型错误。TypeScript 现在能够捕获这些错误,并在编译时提示相应的错误:

|

const proto = {};

Object.create(proto); // OK

Object.create(null); // OK

Object.create(undefined); // Error

Object.create(1337); // Error

Object.create(``true``); // Error

Object.create(``"oops"``); // Error

|

object 类型的另一个用例是作为 ES2015 的一部分引入的 WeakMap 数据结构。它的键必须是对象,不能是原始值。这个要求现在反映在类型定义中:

|

interface WeakMap<K extends object, V> {

delete(key: K): boolean;

get(key: K): V | undefined;

has(key: K): boolean;

set``(key: K, value: V): this;

}

|

二、Object vs object vs {}

也许令人困惑的是,TypeScript 定义了几个类型,它们有相似的名字,但是代表不同的概念:

  • object
  • Object
  • {}

我们已经看到了上面的新对象类型。现在让我们讨论 Object{} 表示什么。

2.1 Object 类型

TypeScript 定义了另一个与新的 object 类型几乎同名的类型,那就是 Object 类型。该类型是所有 Object 类的实例的类型。它由以下两个接口来定义:

  • Object 接口定义了 Object.prototype 原型对象上的属性;
  • ObjectConstructor 接口定义了 Object 类的属性。

下面我们来看一下上述两个接口的相关定义:

1、Object 接口定义

|

// node_modules``/typescript/lib/lib``.es5.d.ts

interface Object {

constructor: Function;

toString(): string;

toLocaleString(): string;

valueOf(): Object;

hasOwnProperty(``v``: PropertyKey): boolean;

isPrototypeOf(``v``: Object): boolean;

propertyIsEnumerable(``v``: PropertyKey): boolean;

}

|

2、ObjectConstructor 接口定义

|

// node_modules``/typescript/lib/lib``.es5.d.ts

interface ObjectConstructor {

/** Invocation vianew*/

new(value?: any): Object;

/** Invocation via function calls */

(value?: any): any;

readonly prototype: Object;

getPrototypeOf(o: any): any;

// ···

}

declare var Object: ObjectConstructor;

|

Object 类的所有实例都继承了 Object 接口中的所有属性。我们可以看到,如果我们创建一个返回其参数的函数:

传入一个 Object 对象的实例,它总是会满足该函数的返回类型 —— 即要求返回值包含一个 toString() 方法。

|

// Object: Provides functionality common to all JavaScript objects.

function f(x: Object): { toString(): string } {

return x; // OK

}

|

object 类型,它用于表示非原始类型(undefined, null, boolean, number, bigint, string, symbol)。使用这种类型,我们不能访问值的任何属性。

2.2 Object vs object

有趣的是,类型 Object 包括原始值:

|

function func1(x: Object) { }

func1(``'semlinker'``); // OK

|

为什么?Object.prototype 的属性也可以通过原始值访问:

|

> 'semlinker'``.hasOwnProperty === Object.prototype.hasOwnProperty

true

|

感兴趣的读者,可以自行了解一下 “JavaScript 装箱和拆箱” 的相关内容。

相反,object 类型不包括原始值:

|

function func2(x: object) { }

// Argument of type '"semlinker"'

// is not assignable to parameter of type 'object'``.(2345)

func2(``'semlinker'``); // Error

|

需要注意的是,当对 Object 类型的变量进行赋值时,如果值对象属性名与 Object 接口中的属性冲突,则 TypeScript 编译器会提示相应的错误:

|

// Type '() => number' is not assignable to type

// '() => string'``.

// Type 'number' is not assignable to type 'string'``.

const obj1: Object = {

toString() { return 123 } // Error

};

|

而对于 object 类型来说,TypeScript 编译器不会提示任何错误:

|

const obj2: object = {

toString() { return 123 }

};

|

另外在处理 object 类型和字符串索引对象类型的赋值操作时,也要特别注意。比如:

|

let strictTypeHeaders: { [key: string]: string } = {};

let header: object = {};

header = strictTypeHeaders; // OK

// Type 'object' is not assignable to type '{ [key: string]: string; }'``.

strictTypeHeaders = header; // Error

|

在上述例子中,最后一行会出现编译错误,这是因为 { [key: string]: string } 类型相比 object 类型更加精确。而 header = strictTypeHeaders; 这一行却没有提示任何错误,是因为这两种类型都是非基本类型,object 类型比 { [key: string]: string } 类型更加通用。

2.3 空类型 {}

还有另一种类型与之非常相似,即空类型:{}。它描述了一个没有成员的对象。当你试图访问这样一个对象的任意属性时,TypeScript 会产生一个编译时错误:

|

// Type {}

const obj = {};

// Error: Property 'prop' does not exist on type '{}'``.

obj.prop = "semlinker"``;

|

但是,你仍然可以使用在 Object 类型上定义的所有属性和方法,这些属性和方法可通过 JavaScript 的原型链隐式地使用:

|

// Type {}

const obj = {};

// "[object Object]"

obj.toString();

|

在 JavaScript 中创建一个表示二维坐标点的对象很简单:

|

const pt = {};

pt.x = 3;

pt.y = 4;

|

然而以上代码在 TypeScript 中,每个赋值语句都会产生错误:

|

const pt = {}; // (A)

// Property 'x' does not exist on type '{}'

pt.x = 3; // Error

// Property 'y' does not exist on type '{}'

pt.y = 4; // Error

|

这是因为第 A 行中的 pt 类型是根据它的值 {} 推断出来的,你只可以对已知的属性赋值。这个问题怎么解决呢?有些读者可能会先想到接口,比如这样子:

|

interface Point {

x: number;

y: number;

}

// Type '{}' is missing the following

// properties from type 'Point'``: x, y(2739)

const pt: Point = {}; // Error

pt.x = 3;

pt.y = 4;

|

很可惜对于以上的方案,TypeScript 编译器仍会提示错误。那么这个问题该如何解决呢?其实我们可以直接通过对象字面量进行赋值:

|

const pt = {

x: 3,

y: 4,

}; // OK

|

而如果你需要一步一步地创建对象,你可以使用类型断言(as)来消除 TypeScript 的类型检查:

|

const pt = {} as Point;

pt.x = 3;

pt.y = 4; // OK

|

但是更好的方法是声明变量的类型并一次性构建对象:

|

const pt: Point = {

x: 3,

y: 4,

};

|

另外在使用 Object.assign 方法合并多个对象的时候,你可能也会遇到以下问题:

|

const pt = { x: 666, y: 888 };

const id = { name: "semlinker" };

const namedPoint = {};

Object.assign(namedPoint, pt, id``);

// Property 'name' does not exist on type '{}'``.(2339)

namedPoint.name; // Error

|

这时候你可以使用对象展开运算符 ... 来解决上述问题:

|

const pt = { x: 666, y: 888 };

const id = { name: "semlinker" };

const namedPoint = {...pt, ...``id``}

//``(property) name: string

namedPoint.name // Ok

|

三、对象字面量类型 vs 接口类型

我们除了可以通过 Object 和 object 类型来描述对象之外,也可以通过对象的属性来描述对象:

|

// Object literal type

let obj3: { prop: boolean };

// Interface

interface ObjectType {

prop: boolean;

}

let obj4: ObjectType;

|

在 TypeScript 中有两种定义对象类型的方法,它们非常相似:

|

// Object literal type

type ObjType1 = {

a: boolean,

b: number;

c: string,

};

// Interface

interface ObjType2 {

a: boolean,

b: number;

c: string,

}

|

在以上代码中,我们使用分号或逗号作为分隔符。尾随分隔符是允许的,也是可选的。好的,那么现在问题来了,对象字面量类型和接口类型之间有什么区别呢?下面我从以下几个方面来分析一下它们之间的区别:

3.1 内联

对象字面量类型可以内联,而接口不能:

|

// Inlined object literal type``:

function f1(x: { prop: number }) {}

function f2(x: ObjectInterface) {} // referenced interface

interface ObjectInterface {

prop: number;

}

|

3.2 名称重复

含有重复名称的类型别名是非法的:

|

// @ts-ignore: Duplicate identifier 'PersonAlias'``. (2300)

type PersonAlias = {first: string};

// @ts-ignore: Duplicate identifier 'PersonAlias'``. (2300)

type PersonAlias = {last: string};

|

TypeScript 2.6 支持在 .ts 文件中通过在报错一行上方使用
// @ts-ignore 来忽略错误。

// @ts-ignore 注释会忽略下一行中产生的所有错误。建议实践中在 @ts-ignore之后添加相关提示,解释忽略了什么错误。

请注意,这个注释仅会隐藏报错,并且我们建议你少使用这一注释。

相反,含有重复名称的接口将会被合并:

|

interface PersonInterface {

first: string;

}

interface PersonInterface {

last: string;

}

const sem: PersonInterface = {

first: 'Jiabao'``,

last: 'Huang'``,

};

|

3.3 映射类型

对于映射类型(A行),我们需要使用对象字面量类型:

|

interface Point {

x: number;

y: number;

}

type PointCopy1 = {

[Key in keyof Point]: Point[Key]; // (A)

};

// Syntax error:

// interface PointCopy2 {

// [Key in keyof Point]: Point[Key];

// };

|

3.4 多态 this 类型

多态 this 类型仅适用于接口:

|

interface AddsStrings {

add(str: string): this;

};

class StringBuilder implements AddsStrings {

result = ''``;

add(str: string) {

this.result += str;

return this;

}

}

|

四、总结

相信很多刚接触 TypeScript 的读者,看到 Object、object 和 {} 这几种类型时,也会感到疑惑。因为不知道它们之间的有什么区别,什么时候使用?为了让读者能更直观的了解到它们之间的区别,最后我们来做个总结:

4.1 object 类型

object 类型是:TypeScript 2.2 引入的新类型,它用于表示非原始类型。

|

// node_modules``/typescript/lib/lib``.es5.d.ts

interface ObjectConstructor {

create(o: object | null): any;

// ...

}

const proto = {};

Object.create(proto); // OK

Object.create(null); // OK

Object.create(undefined); // Error

Object.create(1337); // Error

Object.create(``true``); // Error

Object.create(``"oops"``); // Error

|

4.2 Object 类型

Object 类型:它是所有 Object 类的实例的类型。它由以下两个接口来定义:

它由以下两个接口来定义:

  • Object 接口定义了 Object.prototype 原型对象上的属性;

|

// node_modules``/typescript/lib/lib``.es5.d.ts

interface Object {

constructor: Function;

toString(): string;

toLocaleString(): string;

valueOf(): Object;

hasOwnProperty(``v``: PropertyKey): boolean;

isPrototypeOf(``v``: Object): boolean;

propertyIsEnumerable(``v``: PropertyKey): boolean;

}

|

  • ObjectConstructor 接口定义了 Object 类的属性。

|

// node_modules``/typescript/lib/lib``.es5.d.ts

interface ObjectConstructor {

/** Invocation vianew*/

new(value?: any): Object;

/** Invocation via function calls */

(value?: any): any;

readonly prototype: Object;

getPrototypeOf(o: any): any;

// ···

}

declare var Object: ObjectConstructor;

|

Object 类的所有实例都继承了 Object 接口中的所有属性。

4.3 {} 类型

{} 类型:它描述了一个没有成员的对象。当你试图访问这样一个对象的任意属性时,TypeScript 会产生一个编译时错误。

|

// Type {}

const obj = {};

// Error: Property 'prop' does not exist on type '{}'``.

obj.prop = "semlinker"``;

|

但是,你仍然可以使用在 Object 类型上定义的所有属性和方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容