建立了一個 「重學TypeScript」 的微信羣,想加羣的小夥伴,加我微信 "semlinker",備註重學TS。本文是 」重學TS「 系列,第 27 篇文章,感謝您的閱讀!javascript
TypeScript 2.2 引入了被稱爲 object
類型的新類型,它用於表示非原始類型。在 JavaScript 中如下類型被視爲原始類型:string
、boolean
、number
、bigint
、symbol
、null
和 undefined
。html
全部其餘類型均被視爲非基本類型。新的 object
類型表示以下:java
// All primitive types type Primitive = string | boolean | number | bigint | symbol | null | undefined; // All non-primitive types type NonPrimitive = object;
讓咱們看看 object
類型,如何讓咱們編寫更精確的類型聲明。node
隨着 TypeScript 2.2 的發佈,標準庫的類型聲明已經更新,以使用新的對象類型。例如,Object.create()
和Object.setPrototypeOf()
方法,如今須要爲它們的原型參數指定 object | null
類型:typescript
// 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 如今可以捕獲這些錯誤,並在編譯時提示相應的錯誤:shell
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 數據結構。它的鍵必須是對象,不能是原始值。這個要求如今反映在類型定義中:segmentfault
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; }
也許使人困惑的是,TypeScript 定義了幾個類型,它們有類似的名字,可是表明不一樣的概念:微信
object
Object
{}
咱們已經看到了上面的新對象類型。如今讓咱們討論 Object
和 {}
表示什麼。數據結構
TypeScript 定義了另外一個與新的 object
類型幾乎同名的類型,那就是 Object
類型。該類型是全部 Object 類的實例的類型。它由如下兩個接口來定義:ide
下面咱們來看一下上述兩個接口的相關定義:
一、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; }
二、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 類的全部實例都繼承了 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)。使用這種類型,咱們不能訪問值的任何屬性。
有趣的是,類型 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 }
類型更加通用。
還有另外一種類型與之很是類似,即空類型:{}
。它描述了一個沒有成員的對象。當你試圖訪問這樣一個對象的任意屬性時,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
咱們除了能夠經過 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, }
在以上代碼中,咱們使用分號或逗號做爲分隔符。尾隨分隔符是容許的,也是可選的。好的,那麼如今問題來了,對象字面量類型和接口類型之間有什麼區別呢?下面我從如下幾個方面來分析一下它們之間的區別:
對象字面量類型能夠內聯,而接口不能:
// Inlined object literal type: function f1(x: { prop: number }) {} function f2(x: ObjectInterface) {} // referenced interface interface ObjectInterface { prop: number; }
含有重複名稱的類型別名是非法的:
// @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', };
對於映射類型(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]; // };
多態 this 類型僅適用於接口:
interface AddsStrings { add(str: string): this; }; class StringBuilder implements AddsStrings { result = ''; add(str: string) { this.result += str; return this; } }
相信不少剛接觸 TypeScript 的讀者,看到 Object、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
Object 類型:它是全部 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; }
// 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 類的全部實例都繼承了 Object 接口中的全部屬性。
{} 類型:它描述了一個沒有成員的對象。當你試圖訪問這樣一個對象的任意屬性時,TypeScript 會產生一個編譯時錯誤。
// Type {} const obj = {}; // Error: Property 'prop' does not exist on type '{}'. obj.prop = "semlinker";
可是,你仍然可使用在 Object 類型上定義的全部屬性和方法。
本人的全棧修仙之路訂閱號,會按期分享 Angular、TypeScript、Node.js/Java 、Spring 相關文章,歡迎感興趣的小夥伴訂閱哈!