ES5/ES3 的生成器(Generators)與迭代(Iteration)
首先,瞭解一些 ES2016 術語
迭代器(Iterators)
ES2015 引入了 Iterator,它是一個公開了 next、return 和 throw 三個方法的物件,具體介面如下:
tsinterface Iterator<T> {next(value?: any): IteratorResult<T>;return?(value?: any): IteratorResult<T>;throw?(e?: any): IteratorResult<T>;}
這種型別的迭代器對於遍歷同步獲取的值(例如陣列的元素或 Map 的鍵)非常有用。如果一個物件具有返回 Iterator 物件的 Symbol.iterator 方法,則稱該物件是“可迭代的”(iterable)。
迭代器協議還定義了某些 ES2015 特性的目標,例如 for..of、展開運算子(spread operator)以及解構賦值中的陣列剩餘部分(array rest)。
生成器(Generators)
ES2015 還引入了“生成器”(Generators),它們是可以透過 Iterator 介面和 yield 關鍵字用於產生部分計算結果的函式。生成器還可以透過 yield * 在內部將呼叫委託給另一個可迭代物件。例如:
tsfunction* f() {yield 1;yield* [2, 3];}
新的 --downlevelIteration
之前,生成器僅在目標環境為 ES6/ES2015 或更高版本時才受支援。此外,操作迭代器協議的結構(例如 for..of)在 ES6/ES2015 以下的目標版本中,僅在運算元組時才受支援。
TypeScript 2.3 透過 downlevelIteration 標誌,為 ES3 和 ES5 目標版本增加了對生成器和迭代器協議的全面支援。
使用 downlevelIteration 時,編譯器會使用新的型別檢查和編譯行為:如果找到 [Symbol.iterator]() 方法,它會嘗試在被迭代的物件上呼叫該方法;如果找不到,則會在該物件上建立一個合成的陣列迭代器。
請注意,對於任何非陣列值,這需要在執行時具備原生的
Symbol.iterator或Symbol.iterator補丁(shim)。
當使用 downlevelIteration 時,for..of 語句、陣列解構以及陣列、呼叫和構造表示式中的展開元素,如果可用,都支援 Symbol.iterator。但即使在執行時或設計時未定義 Symbol.iterator,它們仍然可以用於陣列。
非同步迭代(Async Iteration)
TypeScript 2.3 增加了對非同步迭代器和生成器的支援,如當前的 TC39 提案所述。
非同步迭代器(Async iterators)
非同步迭代引入了 AsyncIterator,它類似於 Iterator。不同之處在於,AsyncIterator 的 next、return 和 throw 方法返回的是迭代結果的 Promise,而不是結果本身。這允許呼叫者在 AsyncIterator 推進到產生值的時刻進行非同步通知註冊。AsyncIterator 具有以下形態:
tsinterface AsyncIterator<T> {next(value?: any): Promise<IteratorResult<T>>;return?(value?: any): Promise<IteratorResult<T>>;throw?(e?: any): Promise<IteratorResult<T>>;}
如果一個物件具有返回 AsyncIterator 物件的 Symbol.asyncIterator 方法,則稱該物件是“可非同步迭代的”(async-iterable)。
非同步生成器(Async Generators)
非同步迭代提案引入了“非同步生成器”,它們是同樣可以用於產生部分計算結果的非同步函式。非同步生成器還可以透過 yield* 將呼叫委託給可迭代物件或非同步可迭代物件。
tsasync function* g() {yield 1;await sleep(100);yield* [2, 3];yield* (async function*() {await sleep(100);yield 4;})();}
與生成器一樣,非同步生成器只能是函式宣告、函式表示式或類/物件字面量的方法。箭頭函式不能作為非同步生成器。除了有效的 Symbol.asyncIterator 引用(原生 Symbol 或補丁)外,非同步生成器還需要一個有效的全域性 Promise 實現(原生實現或與 ES2015 相容的 polyfill)。
for-await-of 語句
最後,ES2015 引入了 for..of 語句作為遍歷可迭代物件的一種方式。同樣,非同步迭代提案引入了 for..await..of 語句來遍歷非同步可迭代物件。
tsasync function f() {for await (const x of g()) {console.log(x);}}
for..await..of 語句僅在非同步函式或非同步生成器內合法。
注意事項
- 請記住,我們對非同步迭代器的支援依賴於執行時對
Symbol.asyncIterator的支援。您可能需要對Symbol.asyncIterator進行 polyfill,簡單的實現可以是:(Symbol as any).asyncIterator = Symbol.asyncIterator || Symbol.for("Symbol.asyncIterator"); - 如果您還沒有
AsyncIterator宣告,還需要在lib選項中包含esnext。 - 最後,如果您的目標版本是 ES5 或 ES3,您還需要設定
--downlevelIterators標誌。
泛型引數預設值
TypeScript 2.3 增加了對宣告泛型型別引數預設值的支援。
示例
考慮一個建立新 HTMLElement 的函式,無引數呼叫它時生成一個 Div;您還可以選擇性地傳遞子元素列表。以前您必須這樣定義它:
tsdeclare function create(): Container<HTMLDivElement, HTMLDivElement[]>;declare function create<T extends HTMLElement>(element: T): Container<T, T[]>;declare function create<T extends HTMLElement, U extends HTMLElement>(element: T,children: U[]): Container<T, U[]>;
使用泛型引數預設值,我們可以將其簡化為:
tsdeclare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(element?: T,children?: U): Container<T, U>;
泛型引數預設值遵循以下規則:
- 如果型別引數具有預設值,則視為可選。
- 必選型別引數不能跟在可選型別引數後面。
- 型別引數的預設型別必須滿足該型別引數的約束(如果存在)。
- 指定型別實參時,只需為必選型別引數指定型別實參。未指定的型別引數將解析為它們的預設型別。
- 如果指定了預設型別且推理無法選擇候選者,則推斷使用該預設型別。
- 與現有類或介面宣告合併的類或介面宣告,可以為現有的型別引數引入預設值。
- 與現有類或介面宣告合併的類或介面宣告,可以引入新的型別引數,只要它指定了預設值。
新的 --strict 總控選項
TypeScript 中新增的新檢查預設通常是關閉的,以避免破壞現有專案。雖然避免破壞是件好事,但這種策略的缺點是使得選擇最高級別的型別安全變得日益複雜,且每次 TypeScript 釋出時都需要採取顯式的選擇加入行動。透過 strict 選項,現在可以實現選擇最高級別的型別安全,前提是需要理解隨著改進的型別檢查特性的新增,編譯器可能會報告額外的錯誤。
新的 strict 編譯器選項代表了多項型別檢查選項的推薦設定。具體來說,指定 strict 相當於指定了以下所有選項(將來可能會包含更多選項):
精確地說,strict 選項為上述列出的編譯器選項設定了預設值。這意味著仍然可以單獨控制這些選項。例如:
sh--strict --noImplicitThis false
這將起到開啟所有嚴格選項除了 noImplicitThis 選項的效果。使用此方案,可以表達由所有嚴格選項組成的配置,但排除某些明確列出的選項。換句話說,現在可以預設採用最高級別的型別安全,但可以選擇關閉某些檢查。
從 TypeScript 2.3 開始,由 tsc --init 生成的預設 tsconfig.json 在 "compilerOptions" 部分包含一個 "strict": true 設定。因此,使用 tsc --init 啟動的新專案將預設啟用最高級別的型別安全。
增強的 --init 輸出
除了預設開啟 strict 外,tsc --init 還改進了輸出。由 tsc --init 生成的預設 tsconfig.json 檔案現在包含了一組常用編譯器選項及其描述,並已註釋掉。只需取消註釋您想要設定的配置即可獲得預期的行為;我們希望新的輸出能夠簡化新專案的設定,並在專案增長時保持配置檔案清晰可讀。
使用 --checkJs 檢查 .js 檔案中的錯誤
預設情況下,TypeScript 編譯器不會報告 .js 檔案中的任何錯誤,即使使用了 allowJs。透過 TypeScript 2.3,可以使用 checkJs 在 .js 檔案中報告型別檢查錯誤。
您可以透過新增 // @ts-nocheck 註釋來跳過對某些檔案的檢查;相反,您可以選擇透過新增 // @ts-check 註釋來僅檢查少數幾個 .js 檔案,而無需設定 checkJs。您還可以透過在特定行前新增 // @ts-ignore 來忽略該行的錯誤。
.js 檔案仍會進行檢查以確保它們僅包含標準的 ECMAScript 特性;型別註解僅允許在 .ts 檔案中使用,在 .js 檔案中會被標記為錯誤。JSDoc 註釋可以用於向 JavaScript 程式碼新增一些型別資訊,有關支援的 JSDoc 結構的詳細資訊,請參閱 JSDoc 支援文件。
有關更多詳細資訊,請參閱 型別檢查 JavaScript 檔案文件。