TypeScript 2.2 引入了被稱為 object
類型的新類型版保,它用于表示非原始類型。在 JavaScript 中以下類型被視為原始類型:string
地淀、boolean
嬉探、number
、bigint
排惨、symbol
、null
和 undefined
碰凶。
所有其他類型均被視為非基本類型暮芭。新的 object
類型表示如下:
//
All primitive types
type
Primitive = string
| boolean | number
| bigint | symbol
| null | undefined;
//
All non-primitive types
type
NonPrimitive = object;
|
讓我們看看 object
類型,如何讓我們編寫更精確的類型聲明欲低。
一辕宏、使用 object 類型進(jìn)行類型聲明
隨著 TypeScript 2.2 的發(fā)布,標(biāo)準(zhǔn)庫的類型聲明已經(jīng)更新砾莱,以使用新的對象類型瑞筐。例如,Object.create()
和Object.setPrototypeOf()
方法腊瑟,現(xiàn)在需要為它們的原型參數(shù)指定 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()
將導(dǎo)致在運(yùn)行時拋出類型錯誤聚假。TypeScript 現(xiàn)在能夠捕獲這些錯誤块蚌,并在編譯時提示相應(yīng)的錯誤:
|
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 數(shù)據(jù)結(jié)構(gòu)。它的鍵必須是對象魔策,不能是原始值匈子。這個要求現(xiàn)在反映在類型定義中:
|
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
{}
我們已經(jīng)看到了上面的新對象類型≌遥現(xiàn)在讓我們討論 Object
和 {}
表示什么其徙。
2.1 Object 類型
TypeScript 定義了另一個與新的 object
類型幾乎同名的類型,那就是 Object
類型喷户。該類型是所有 Object 類的實(shí)例的類型唾那。它由以下兩個接口來定義:
- Object 接口定義了 Object.prototype 原型對象上的屬性;
- ObjectConstructor 接口定義了 Object 類的屬性褪尝。
下面我們來看一下上述兩個接口的相關(guān)定義:
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 via
new*/
new(value?: any): Object;
/** Invocation via
function
calls */
(value?: any): any;
readonly
prototype: Object;
getPrototypeOf(o: any): any;
//
···
}
declare
var Object: ObjectConstructor;
|
Object 類的所有實(shí)例都繼承了 Object 接口中的所有屬性河哑。我們可以看到避诽,如果我們創(chuàng)建一個返回其參數(shù)的函數(shù):
傳入一個 Object 對象的實(shí)例,它總是會滿足該函數(shù)的返回類型 —— 即要求返回值包含一個 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 裝箱和拆箱” 的相關(guān)內(nèi)容。
相反衷模,object
類型不包括原始值:
|
function
func2(x: object) { }
//
Argument of
type
'"semlinker"'
//
is not assignable to parameter of
type
'object'``.(2345)
func2(``'semlinker'``);
//
Error
|
需要注意的是羡滑,當(dāng)對 Object 類型的變量進(jìn)行賦值時,如果值對象屬性名與 Object 接口中的屬性沖突算芯,則 TypeScript 編譯器會提示相應(yīng)的錯誤:
|
//
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
|
在上述例子中,最后一行會出現(xiàn)編譯錯誤氏涩,這是因?yàn)?{ [key: string]: string }
類型相比 object
類型更加精確届囚。而 header = strictTypeHeaders;
這一行卻沒有提示任何錯誤有梆,是因?yàn)檫@兩種類型都是非基本類型,object
類型比 { [key: string]: string }
類型更加通用意系。
2.3 空類型 {}
還有另一種類型與之非常相似泥耀,即空類型:{}
。它描述了一個沒有成員的對象蛔添。當(dāng)你試圖訪問這樣一個對象的任意屬性時痰催,TypeScript 會產(chǎn)生一個編譯時錯誤:
|
//
Type {}
const obj = {};
//
Error: Property
'prop'
does not exist on
type
'{}'``.
obj.prop =
"semlinker"``;
|
但是,你仍然可以使用在 Object 類型上定義的所有屬性和方法迎瞧,這些屬性和方法可通過 JavaScript 的原型鏈隱式地使用:
|
//
Type {}
const obj = {};
//
"[object Object]"
obj.toString();
|
在 JavaScript 中創(chuàng)建一個表示二維坐標(biāo)點(diǎn)的對象很簡單:
|
const pt = {};
pt.x = 3;
pt.y = 4;
|
然而以上代碼在 TypeScript 中夸溶,每個賦值語句都會產(chǎn)生錯誤:
|
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
|
這是因?yàn)榈?A 行中的 pt 類型是根據(jù)它的值 {} 推斷出來的,你只可以對已知的屬性賦值凶硅。這個問題怎么解決呢缝裁?有些讀者可能會先想到接口,比如這樣子:
|
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 編譯器仍會提示錯誤捷绑。那么這個問題該如何解決呢?其實(shí)我們可以直接通過對象字面量進(jìn)行賦值:
|
const pt = {
x: 3,
y: 4,
};
//
OK
|
而如果你需要一步一步地創(chuàng)建對象氢妈,你可以使用類型斷言(as)來消除 TypeScript 的類型檢查:
|
const pt = {} as Point;
pt.x = 3;
pt.y = 4;
//
OK
|
但是更好的方法是聲明變量的類型并一次性構(gòu)建對象:
|
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
|
這時候你可以使用對象展開運(yùn)算符 ...
來解決上述問題:
|
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,
}
|
在以上代碼中,我們使用分號或逗號作為分隔符蕾总。尾隨分隔符是允許的粥航,也是可選的。好的生百,那么現(xiàn)在問題來了递雀,對象字面量類型和接口類型之間有什么區(qū)別呢?下面我從以下幾個方面來分析一下它們之間的區(qū)別:
3.1 內(nèi)聯(lián)
對象字面量類型可以內(nèi)聯(lián)蚀浆,而接口不能:
|
//
Inlined object literal
type``:
function
f1(x: { prop: number }) {}
function
f2(x: ObjectInterface) {}
//
referenced interface
interface ObjectInterface {
prop: number;
}
|
3.2 名稱重復(fù)
含有重復(fù)名稱的類型別名是非法的:
|
//
@ts-ignore: Duplicate identifier
'PersonAlias'``. (2300)
type
PersonAlias = {first: string};
//
@ts-ignore: Duplicate identifier
'PersonAlias'``. (2300)
type
PersonAlias = {last: string};
|
TypeScript 2.6 支持在 .ts 文件中通過在報(bào)錯一行上方使用
// @ts-ignore
來忽略錯誤缀程。
// @ts-ignore
注釋會忽略下一行中產(chǎn)生的所有錯誤。建議實(shí)踐中在@ts-ignore
之后添加相關(guān)提示市俊,解釋忽略了什么錯誤杨凑。請注意,這個注釋僅會隱藏報(bào)錯摆昧,并且我們建議你少使用這一注釋撩满。
相反,含有重復(fù)名稱的接口將會被合并:
|
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 多態(tài) this 類型
多態(tài) this 類型僅適用于接口:
|
interface AddsStrings {
add(str: string): this;
};
class StringBuilder implements AddsStrings {
result =
''``;
add(str: string) {
this.result += str;
return
this;
}
}
|
四伺帘、總結(jié)
相信很多剛接觸 TypeScript 的讀者昭躺,看到 Object、object 和 {} 這幾種類型時伪嫁,也會感到疑惑领炫。因?yàn)椴恢浪鼈冎g的有什么區(qū)別,什么時候使用张咳?為了讓讀者能更直觀的了解到它們之間的區(qū)別帝洪,最后我們來做個總結(jié):
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 類的實(shí)例的類型碟狞。它由以下兩個接口來定義:
它由以下兩個接口來定義:
- 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 via
new*/
new(value?: any): Object;
/** Invocation via
function
calls */
(value?: any): any;
readonly
prototype: Object;
getPrototypeOf(o: any): any;
//
···
}
declare
var Object: ObjectConstructor;
|
Object 類的所有實(shí)例都繼承了 Object 接口中的所有屬性族沃。
4.3 {} 類型
{} 類型:它描述了一個沒有成員的對象。當(dāng)你試圖訪問這樣一個對象的任意屬性時泌参,TypeScript 會產(chǎn)生一個編譯時錯誤脆淹。
|
//
Type {}
const obj = {};
//
Error: Property
'prop'
does not exist on
type
'{}'``.
obj.prop =
"semlinker"``;
|
但是,你仍然可以使用在 Object 類型上定義的所有屬性和方法沽一。