1. 程式人生 > 其它 >typescript中Object,object,{}型別之間的區別

typescript中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;
// ...
}
1
2
3
4
5
6
將原始型別作為原型傳遞給 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

1
2
3
4
5
6
7
8
9
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;
}
1
2
3
4
5
6
二、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;
}
1
2
3
4
5
6
7
8
9
10
11
2、ObjectConstructor 介面定義

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

interface ObjectConstructor {
/** Invocation via `new` */
new(value?: any): Object;
/** Invocation via function calls */
(value?: any): any;

readonly prototype: Object;

getPrototypeOf(o: any): any;

// ···
}

declare var Object: ObjectConstructor;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object 類的所有例項都繼承了 Object 介面中的所有屬性。我們可以看到,如果我們建立一個返回其引數的函式:

傳入一個 Object 物件的例項,它總是會滿足該函式的返回型別 —— 即要求返回值包含一個 toString() 方法。

// Object: Provides functionality common to all JavaScript objects.
function f(x: Object): { toString(): string } {
return x; // OK
}
1
2
3
4
而 object 型別,它用於表示非原始型別(undefined, null, boolean, number, bigint, string, symbol)。使用這種型別,我們不能訪問值的任何屬性。

2.2 Object vs object
有趣的是,型別 Object 包括原始值:

function func1(x: Object) { }
func1('semlinker'); // OK
1
2
為什麼?Object.prototype 的屬性也可以通過原始值訪問:

> 'semlinker'.hasOwnProperty === Object.prototype.hasOwnProperty
true
1
2
感興趣的讀者,可以自行了解一下 “JavaScript 裝箱和拆箱” 的相關內容。

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

function func2(x: object) { }

// Argument of type '"semlinker"'
// is not assignable to parameter of type 'object'.(2345)
func2('semlinker'); // Error
1
2
3
4
5
需要注意的是,當對 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
};
1
2
3
4
5
6
而對於 object 型別來說,TypeScript 編譯器不會提示任何錯誤:

const obj2: object = {
toString() { return 123 }
};
1
2
3
另外在處理 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
1
2
3
4
5
在上述例子中,最後一行會出現編譯錯誤,這是因為 { [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";
1
2
3
4
5
但是,你仍然可以使用在 Object 型別上定義的所有屬性和方法,這些屬性和方法可通過 JavaScript 的原型鏈隱式地使用:

// Type {}
const obj = {};

// "[object Object]"
obj.toString();
1
2
3
4
5
在 JavaScript 中建立一個表示二維座標點的物件很簡單:

const pt = {};
pt.x = 3;
pt.y = 4;
1
2
3
然而以上程式碼在 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
1
2
3
4
5
這是因為第 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;
1
2
3
4
5
6
7
8
9
10
很可惜對於以上的方案,TypeScript 編譯器仍會提示錯誤。那麼這個問題該如何解決呢?其實我們可以直接通過物件字面量進行賦值:

const pt = {
x: 3,
y: 4,
}; // OK
1
2
3
4
而如果你需要一步一步地建立物件,你可以使用型別斷言(as)來消除 TypeScript 的型別檢查:

const pt = {} as Point;
pt.x = 3;
pt.y = 4; // OK
1
2
3
但是更好的方法是宣告變數的型別並一次性構建物件:

const pt: Point = {
x: 3,
y: 4,
};
1
2
3
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
1
2
3
4
5
6
7
這時候你可以使用物件展開運算子 … 來解決上述問題:

const pt = { x: 666, y: 888 };
const id = { name: "semlinker" };
const namedPoint = {...pt, ...id}

//(property) name: string
namedPoint.name // Ok
1
2
3
4
5
6
三、物件字面量型別 vs 介面型別
我們除了可以通過 Object 和 object 型別來描述物件之外,也可以通過物件的屬性來描述物件

// Object literal type
let obj3: { prop: boolean };

// Interface
interface ObjectType {
prop: boolean;
}

let obj4: ObjectType;
1
2
3
4
5
6
7
8
9
在 TypeScript 中有兩種定義物件型別的方法,它們非常相似:

// Object literal type
type ObjType1 = {
a: boolean,
b: number;
c: string,
};

// Interface
interface ObjType2 {
a: boolean,
b: number;
c: string,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
在以上程式碼中,我們使用分號或逗號作為分隔符。尾隨分隔符是允許的,也是可選的。好的,那麼現在問題來了,物件字面量型別和介面型別之間有什麼區別呢?下面我從以下幾個方面來分析一下它們之間的區別:

3.1 內聯
物件字面量型別可以內聯,而介面不能:

// Inlined object literal type:
function f1(x: { prop: number }) {}

function f2(x: ObjectInterface) {} // referenced interface
interface ObjectInterface {
prop: number;
}
1
2
3
4
5
6
7
3.2 名稱重複
含有重複名稱的類型別名是非法的:

// @ts-ignore: Duplicate identifier 'PersonAlias'. (2300)
type PersonAlias = {first: string};

// @ts-ignore: Duplicate identifier 'PersonAlias'. (2300)
type PersonAlias = {last: string};
1
2
3
4
5
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',
};
1
2
3
4
5
6
7
8
9
10
11
12
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];
// };
1
2
3
4
5
6
7
8
9
10
11
12
13
3.4 多型 this 型別
多型 this 型別僅適用於介面:

interface AddsStrings {
add(str: string): this;
};

class StringBuilder implements AddsStrings {
result = '';
add(str: string) {
this.result += str;
return this;
}
}
1
2
3
4
5
6
7
8
9
10
11
四、總結
相信很多剛接觸 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}
1
2
3
4
5
6
7
8
9
10
11
ObjectConstructor 介面定義了 Object 類的屬性。
// node_modules/typescript/lib/lib.es5.d.ts

interface ObjectConstructor {
/** Invocation via `new` */
new(value?: any): Object;
/** Invocation via function calls */
(value?: any): any;

readonly prototype: Object;

getPrototypeOf(o: any): any;

// ···
}

declare var Object: ObjectConstructor;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object 類的所有例項都繼承了 Object 介面中的所有屬性。
4.3 {} 型別
{} 型別:它描述了一個沒有成員的物件。當你試圖訪問這樣一個物件的任意屬性時,TypeScript 會產生一個編譯時錯誤。

// Type {}
const obj = {};

// Error: Property 'prop' does not exist on type '{}'.
obj.prop = "semlinker";
1
2
3
4
5
但是,你仍然可以使用在 Object 型別上定義的所有屬性和方法。
————————————————
版權宣告:本文為CSDN博主「金剛腿」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/GoldenLegs/article/details/112965682

漫思