詳解TS數(shù)字分隔符和更嚴(yán)格的類屬性檢查
概述
TypeScript 2.4 為標(biāo)識符實現(xiàn)了拼寫糾正機制。即使咱們稍微拼錯了一個變量、屬性或函數(shù)名,TypeScript 在很多情況下都可以提示正確的拼寫。
TypeScript 2.7 支持 ECMAScript 的數(shù)字分隔符提案。 這個特性允許用戶在數(shù)字之間使用下劃線(_)來對數(shù)字分組(就像使用逗號和點來對數(shù)字分組那樣)。
const worldPopulationIn2017 = 7_600_000_000; const leastSignificantByteMask = 0b1111_1111; const papayawhipColorHexCode = 0xFF_EF_D5;
數(shù)字分隔符不會改變數(shù)字字面量的值,但分組使人們更容易一眼就能讀懂?dāng)?shù)字。
這些分隔符對于二進制和十六進制同樣有用。
let bits = 0b0010_1010; let routine = 0xC0FFEE_F00D_BED; let martin = 0xF0_1E_
注意,可能有些反常識,js里的數(shù)字表示信用卡和電話號并不適當(dāng),這種情況下使用字符串更好。
當(dāng)咱們將target設(shè)置為es2015編譯的上述代碼時,TypeScript 將生成以下js代碼:
const worldPopulationIn2017 = 7600000000; const leastSignificantByteMask = 255; const papayawhipColorHexCode = 16773077;
in操作符細化和精確的 instanceof
TypeScript 2.7帶來了兩處類型細化方面的改動 - 通過執(zhí)行“類型保護”確定更詳細類型的能力。
首先,instanceof操作符現(xiàn)在利用繼承鏈而非依賴于結(jié)構(gòu)兼容性, 能更準(zhǔn)確地反映出 instanceof操作符在運行時的行為。 這可以幫助避免一些復(fù)雜的問題,當(dāng)使用 instanceof去細化結(jié)構(gòu)上相似(但無關(guān))的類型時。
其次,in操作符現(xiàn)在做為類型保護使用,會細化掉沒有明確聲明的屬性名。
interface A { a: number }; interface B { b: string }; function foo(x: A | B) { if ("a" in x) { return x.a; } return x.b; }
更智能的對象字面量推斷
在 JS 里有一種模式,用戶會忽略掉一些屬性,稍后在使用的時候那些屬性的值為undefined。
let foo = someTest ? { value: 42 } : {};
在以前TypeScript會查找{ value: number }和{}的最佳超類型,結(jié)果是{}。 這從技術(shù)角度上講是正確的,但并不是很有用。
從2.7版本開始,TypeScript 會“規(guī)范化”每個對象字面量類型記錄每個屬性, 為每個undefined類型屬性插入一個可選屬性,并將它們聯(lián)合起來。
在上例中,foo的最類型是{ value: number } | { value?: undefined }。 結(jié)合了 TypeScript 的細化類型,這讓咱們可以編寫更具表達性的代碼且 TypeScript 也可理解。 看另外一個例子:
// Has type // | { a: boolean, aData: number, b?: undefined } // | { b: boolean, bData: string, a?: undefined } let bar = Math.random() < 0.5 ? { a: true, aData: 100 } : { b: true, bData: "hello" }; if (bar.b) { // TypeScript now knows that 'bar' has the type // // '{ b: boolean, bData: string, a?: undefined }' // // so it knows that 'bData' is available. bar.bData.toLowerCase() }
這里,TypeScript 可以通過檢查b屬性來細化bar的類型,然后允許我們訪問bData屬性。
unique symbol 類型和常量名屬性
TypeScript 2.7 對ECMAScript里的symbols有了更深入的了解,你可以更靈活地使用它們。
一個需求很大的用例是使用symbols來聲明一個類型良好的屬性。 比如,看下面的例子:
const Foo = Symbol("Foo"); const Bar = Symbol("Bar"); let x = { [Foo]: 100, [Bar]: "hello", }; let a = x[Foo]; // has type 'number' let b = x[Bar]; // has type 'string'
可以看到,TypeScript 可以追蹤到x擁有使用符號Foo和Bar聲明的屬性,因為Foo和Bar被聲明成常量。 TypeScript 利用了這一點,讓Foo和Bar具有了一種新類型:unique symbols。
unique symbols是symbols的子類型,僅可通過調(diào)用Symbol()或Symbol.for()或由明確的類型注釋生成。 它們僅出現(xiàn)在常量聲明和只讀的靜態(tài)屬性上,并且為了引用一個存在的unique symbols類型,你必須使用typeof操作符。 每個對unique symbols的引用都意味著一個完全唯一的聲明身份。
// Works declare const Foo: unique symbol; // Error! 'Bar' isn't a constant. let Bar: unique symbol = Symbol(); // Works - refers to a unique symbol, but its identity is tied to 'Foo'. let Baz: typeof Foo = Foo; // Also works. class C { static readonly StaticSymbol: unique symbol = Symbol(); }
因為每個unique symbols都有個完全獨立的身份,因此兩個unique symbols類型之前不能賦值和比較。
const Foo = Symbol(); const Bar = Symbol(); // Error: can't compare two unique symbols. if (Foo === Bar) { // ... }
另一個可能的用例是使用 symbols做為聯(lián)合標(biāo)記。
// ./ShapeKind.ts export const Circle = Symbol("circle"); export const Square = Symbol("square"); // ./ShapeFun.ts import * as ShapeKind from "./ShapeKind"; interface Circle { kind: typeof ShapeKind.Circle; radius: number; } interface Square { kind: typeof ShapeKind.Square; sideLength: number; } function area(shape: Circle | Square) { if (shape.kind === ShapeKind.Circle) { // 'shape' has type 'Circle' return Math.PI * shape.radius ** 2; } // 'shape' has type 'Square' return shape.sideLength ** 2; }
更嚴(yán)格的類屬性檢查
TypeScript 2.7 引入了一個新的編譯器選項,用于類中嚴(yán)格的屬性初始化檢查。如果啟用了--strictPropertyInitialization標(biāo)志,則類型檢查器將驗證類中聲明的每個實例屬性
- 是否有包含undefined的類型
- 有一個明確的初始值設(shè)定項
- 在構(gòu)造函數(shù)中被明確賦值
--strictPropertyInitialization選項是編譯器選項系列的一部分,當(dāng)設(shè)置--strict標(biāo)志時,該選項會自動啟用。 與所有其他嚴(yán)格的編譯器選項一樣,咱們可以將--strict設(shè)置為true,并通過將--strictPropertyInitialization設(shè)置為false來有選擇地退出嚴(yán)格的屬性初始化檢查。
請注意,必須設(shè)置--strictNullCheck標(biāo)志(通過—strict直接或間接地設(shè)置),以便--strictPropertyInitialization起作用。
現(xiàn)在,來看看嚴(yán)格的屬性初始化檢查。如果沒有啟用--strictpropertyinitialized標(biāo)志,下面的代碼類型檢查就可以了,但是會在運行時產(chǎn)生一個TypeError錯誤:
class User { username: string; } const user = new User(); // TypeError: Cannot read property 'toLowerCase' of undefined const username = user.username.toLowerCase();
出現(xiàn)運行時錯誤的原因是,username屬性值為undefined,因為沒有對該屬性的賦值。因此,對toLowerCase()方法的調(diào)用失敗。
如果啟用——strictpropertyinitialize,類型檢查器將會報一個錯誤:
class User { // Type error: Property 'username' has no initializer // and is not definitely assigned in the constructor username: string; }
接下來,看看四種不同的方法,可以正確地輸入User類來消除類型錯誤。
解決方案1:允許定義
消除類型錯誤的一種方法是為username屬性提供一個包含undefined的類型:
class User { username: string | undefined; } const user = new User();
現(xiàn)在,username屬性保存undefined的值是完全有效的。但是,當(dāng)咱們想要將username屬性用作字符串時,首先必須確保它實際包含的是字符串而不是undefined的值,例如使用typeof
// OK const username = typeof user.username === "string" ? user.username.toLowerCase() : "n/a";
解決方案2:顯式屬性初始化
消除類型錯誤的另一種方法是向username屬性添加顯式初始化。通過這種方式,屬性將立即保存一個字符串值,并且不會明顯的undefined:
class User { username = "n/a"; } const user = new User(); // OK const username = user.username.toLowerCase();
解決方案3: 使用構(gòu)造函數(shù)賦值
也許最有用的解決方案是將username參數(shù)添加到構(gòu)造函數(shù)中,然后將其分配給username屬性。這樣,每當(dāng)構(gòu)造User類的實例時,調(diào)用者必須提供用戶名作為參數(shù):
class User { username: string; constructor(username: string) { this.username = username; } } const user = new User("mariusschulz"); // OK const username = user.username.toLowerCase();
咱們 還可以通過刪除對類字段的顯式賦值并將public修飾符添加到username構(gòu)造函數(shù)參數(shù)來簡化User類,如下所示:
class User { constructor(public username: string) {} } const user = new User("mariusschulz"); // OK const username = user.username.toLowerCase();
請注意,嚴(yán)格的屬性初始化要求在構(gòu)造函數(shù)中所有可能的代碼路徑中明確分配每個屬性。 因此,以下代碼類型不正確,因為在某些情況下,我們將username屬性賦值為未初始化狀態(tài):
class User { // Type error: Property 'username' has no initializer // and is not definitely assigned in the constructor. username: string; constructor(username: string) { if (Math.random() < 0.5) { this.username = username; } } }
解決方案4:明確的賦值斷言
如果類屬性既沒有顯式初始化,也沒有undefined的類型,則類型檢查器要求直接在構(gòu)造函數(shù)中初始化該屬性;否則,嚴(yán)格的屬性初始化檢查將失敗。如果咱們希望在幫助方法中初始化屬性,或者讓依賴項注入框架來初始化屬性,那么這是有問題的。在這些情況下,咱們必須將一個明確的賦值斷言(!)添加到該屬性的聲明中:
class User { username!: string; constructor(username: string) { this.initialize(username); } private initialize(username: string) { this.username = username; } } const user = new User("mariusschulz"); // OK const username = user.username.toLowerCase();
通過向username屬性添加一個明確的賦值斷言,這會告訴類型檢查器,期望對username屬性進行初始化,即使它自己無法檢測到這一點。現(xiàn)在咱們的責(zé)任是確保在構(gòu)造函數(shù)返回后明確地將屬性賦值給它,所以必須小心;否則,username屬性可能被明顯的undefined或者在運行時就會報TypeError錯誤。
顯式賦值斷言
盡管咱們嘗試將類型系統(tǒng)做的更富表現(xiàn)力,但我們知道有時用戶比TypeScript更加了解類型。
上面提到過,顯式賦值斷言是一個新語法,使用它來告訴 TypeScript 一個屬性會被明確地賦值。 但是除了在類屬性上使用它之外,在TypeScript 2.7里你還可以在變量聲明上使用它!
let x!: number[]; initialize(); x.push(4); function initialize() { x = [0, 1, 2, 3]; }
假設(shè)我們沒有在x后面加上感嘆號,那么TypeScript會報告x從未被初始化過。 它在延遲初始化或重新初始化的場景下很方便使用。
以上就是詳解TS數(shù)字分隔符和更嚴(yán)格的類屬性檢查的詳細內(nèi)容,更多關(guān)于TS的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js nextSibling屬性和previousSibling屬性概述及使用注意
nextSibling屬性:該屬性表示當(dāng)前節(jié)點的下一個節(jié)點;如果其后沒有與其同級的節(jié)點,則返回null;previousSibling屬性:該屬性與nextSibling屬性的作用正好相反,接下來將詳細介紹下,感興趣的你不妨了解下哦,或許對你有所幫助2013-02-02張孝祥JavaScript學(xué)習(xí)階段性總結(jié)(2)--(X)HTML學(xué)習(xí)
張孝祥JavaScript學(xué)習(xí)階段性總結(jié)(2)--(X)HTML學(xué)習(xí)...2007-02-02Javascript開發(fā)之三數(shù)組對象實例介紹
Javascript開發(fā)之三組數(shù)對象詳細介紹,需要的朋友可以參考下2012-11-11JavaScript中Cookies的相關(guān)使用教程
這篇文章主要介紹了JavaScript中Cookies的相關(guān)使用教程,包括Cookies的存儲和刪除等操作方法,需要的朋友可以參考下2015-06-06