做者:Marius Schulz
譯者:前端小智
來源: https://mariusschulz.com/
點贊再看,養成習慣本文
GitHub
https://github.com/qq44924588... 上已經收錄,更多往期高贊文章的分類,也整理了不少個人文檔,和教程資料。歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。javascript
TypeScript 2.3 引入了一個新的--downlevelIteration
標誌,爲以 ES3 和 ES5 目標添加了對 ES6 迭代協議的徹底支持。for...of
循環如今能夠用正確的語義進行向下編譯。html
for...of
遍歷數組假設我們如今的tsconfig.json
設置 target
爲 es5:前端
{ "compilerOptions": { "target": "es5" } }
建立 indtx.ts
文件並輸入如下內容:java
const numbers = [4, 8, 15, 16, 23, 42]; for (const number of numbers) { console.log(number); }
由於它包含任何 TypeScript 特定的語法,因此不須要先經過TypeScript編譯器就能夠直接運行ts
文件:node
$ node index.ts 4 8 15 16 23 42
如今將index.ts
文件編譯成index.js
:webpack
tsc -p .
查看生成的 JS 代碼,能夠看 到TypeScript 編譯器生成了一個傳統的基於索引的for
循環來遍歷數組:git
var numbers = [4, 8, 15, 16, 23, 42]; for (var _i = 0, numbers_1 = numbers; _i < numbers_1.length; _i++) { var number = numbers_1[_i]; console.log(number); }
若是運行這段代碼,能夠正常工做:es6
$ node index.js 4 8 15 16 23 42
運行node index.ts
和node index.js
是徹底相同的,這說明我們沒有經過運行 TypeScript 編譯器來改變程序的行爲。github
在來看看 for...of
的另一個例子,此次我們遍歷的是字符串而不是數組:web
const text = "Booh! 👻"; for (const char of text) { console.log(char); }
一樣,我們能夠直接運行 node index.ts
,由於我們的代碼僅使用ES2015
語法,而沒有TypeScript
專用。
$ node index.ts B o o h ! 👻
如今將index.ts
文件編譯成index.js
。當以 ES3 或 ES5 爲目標時,TypeScript 編譯器將爲上述代碼生成一個基於索引的for
循環的代碼:
var text = "Booh! 👻"; for (var _i = 0, text_1 = text; _i < text_1.length; _i++) { var char = text_1[_i]; console.log(char); }
不幸的是,生成的 JS 代碼的行爲與原始的 TypeScript 版本明顯不一樣:
$ node index.js B o o h ! � �
幽靈表情符號
或代碼 U+1F47B
,更準確地說是由兩個代碼單元U+D83D
和U+DC7B
組成。由於對字符串進行索引將返回該索引處的代碼單元(而不是代碼點),因此生成的for
循環將幽靈表情符分解爲單獨的代碼單元。
另外一方面,字符串迭代協議遍歷字符串的每一個代碼點,這就是兩個程序的輸出不一樣的緣由。經過比較字符串的length
屬性和字符串迭代器生成的序列的長度,能夠肯定它們之間的差別。
const ghostEmoji = "\u{1F47B}"; console.log(ghostEmoji.length); // 2 console.log([...ghostEmoji].length); // 1
簡單的說:當目標爲 ES3 或 ES5 時,使用for...of
循環遍歷字符串並不老是正確。這也是 TypeScript 2.3引入的新--downlevelIteration
標誌緣由。
我們以前的index.ts
:
const text = "Booh! 👻"; for (const char of text) { console.log(char); }
如今我們修改tsconfig.json
文件,並將新的downlevelIteration
標誌設爲true
:
{ "compilerOptions": { "target": "es5", "downlevelIteration": true } }
再次運行編譯器,將生成如下 JS 代碼
var __values = (this && this.__values) || function (o) { var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; if (m) return m.call(o); return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; }; var text = "Booh! 👻"; try { for (var text_1 = __values(text), text_1_1 = text_1.next(); !text_1_1.done; text_1_1 = text_1.next()) { var char = text_1_1.value; console.log(char); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (text_1_1 && !text_1_1.done && (_a = text_1.return)) _a.call(text_1); } finally { if (e_1) throw e_1.error; } } var e_1, _a;
如你所見,生成的代碼比簡單的for
循環複雜得多,這是由於它包含正確的迭代協議實現:
__values
幫助器函數將查找[Symbol.iterator]
方法,若是找到該方法,則將其調用。若是不是,它將在對象上建立一個合成數組迭代器。for
循環無需遍歷每一個代碼單元,而是調用迭代器的next()
方法,直到耗盡爲止,此時,done
爲true
。爲了根據ECMAScript規範實現迭代協議,會生成try/catch/finally
塊以進行正確的錯誤處理。
若是如今再次執行index.js
文件,會獲得正確的結果:
$ node index.js B o o h ! 👻
請注意,若是我們的代碼是在沒有本地定義該symbol
的環境中執行的,則仍然須要Symbol.iterator
的填充程序。例如,在 ES5 環境,若是未定義Symbol.iterator
,則將強制__values
幫助器函數建立不遵循正確迭代協議的綜合數組迭代器。
downlevelIteration
ES2015 增長了新的集合類型,好比Map
和Set
到標準庫。在本節中,將介紹如何使用for...of
循環遍歷Map
。
在下面的示例中,咱建立了一個從數字和它們各自的英文名稱的數組。在構造函數中使用十個鍵值對(表示爲兩個元素的數組)初始化Map
。而後使用for...of
循環和數組解構模式將鍵值對分解爲digit
和name
:
const digits = new Map([ [0, "zero"], [1, "one"], [2, "two"], [3, "three"], [4, "four"], [5, "five"], [6, "six"], [7, "seven"], [8, "eight"], [9, "nine"] ]); for (const [digit, name] of digits) { console.log(`${digit} -> ${name}`); }
這是徹底有效的 ES6 代碼,能夠正常運行:
$ node index.ts 0 -> zero 1 -> one 2 -> two 3 -> three 4 -> four 5 -> five 6 -> six 7 -> seven 8 -> eight 9 -> nine
然而,TypeScript 編譯器並不會這樣認爲,說它找不到Map
:
這是由於我們的目標設置爲ES5
,它沒有實現 Map
。假設我們已經爲Map
提供了一個polyfill
,這樣程序就能夠在運行時運行,那麼我們該如何編譯這段代碼呢
解決方案是將"es2015.collection"
和"es2015.iterable"
值添加到我們的tsconfig.json
文件中的lib
選項中。這告訴 TypeScript 編譯器能夠假定在運行時查找 es6 集合實現和 Symbol.iterator
。
可是,一旦明確指定lib
選項,其默認值將再也不適用,所以,還要添加"dom"
和"es5"
,以即可以訪問其餘標準庫方法。
這是生成的tsconfig.json
:
{ "compilerOptions": { "target": "es5", "downlevelIteration": true, "lib": [ "dom", "es5", "es2015.collection", "es2015.iterable" ] } }
如今,TypeScript 編譯器再也不報錯並生成如下 JS 代碼:
var __values = (this && this.__values) || function (o) { var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; if (m) return m.call(o); return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var digits = new Map([ [0, "zero"], [1, "one"], [2, "two"], [3, "three"], [4, "four"], [5, "five"], [6, "six"], [7, "seven"], [8, "eight"], [9, "nine"] ]); try { for (var digits_1 = __values(digits), digits_1_1 = digits_1.next(); !digits_1_1.done; digits_1_1 = digits_1.next()) { var _a = __read(digits_1_1.value, 2), digit = _a[0], name_1 = _a[1]; console.log(digit + " -> " + name_1); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (digits_1_1 && !digits_1_1.done && (_b = digits_1.return)) _b.call(digits_1); } finally { if (e_1) throw e_1.error; } } var e_1, _b;
在次執行就能正確輸出了。
不過,我們還要注意一件事,如今,生成的 JS 代碼包括兩個輔助函數__values
和__read
,它們增長了代碼大小,接下來我們嘗試削它一下。
--importHelpers
和tslib
減小代碼大小在上面的代碼示例中,__values
和__read
輔助函數被內聯到生成的 JS 代碼中。若是要編譯包含多個文件的 TypeScript 項目,這是很很差的,每一個生成的 JS 文件都包含執行該文件所需的全部幫助程序,從而大大的增長了代碼的大小。
在較好的的項目配置中,我們會使用諸如 webpack 之類的綁定器將全部模塊捆綁在一塊兒。若是 webpack 不止一次地包含一個幫助函數,那麼它生成的包就會沒必要要地大。
解決方案是使用--importHelpers
編譯器選項和tslib
包。當指定時,--importHelpers
會告訴TypeScript 編譯器從tslib
導入全部幫助函數。像 webpack 這樣的捆綁器能夠只內聯一次 npm 包,從而避免代碼重複。
爲了演示--importHelpers
的效果,首先打開index.ts
文件並將函數導出到模塊中
const digits = new Map([ [0, "zero"], [1, "one"], [2, "two"], [3, "three"], [4, "four"], [5, "five"], [6, "six"], [7, "seven"], [8, "eight"], [9, "nine"] ]); export function printDigits() { for (const [digit, name] of digits) { console.log(`${digit} -> ${name}`); } }
如今我們須要修改編譯器配置並將importHelpers
設置爲true
,以下所示:
{ "compilerOptions": { "target": "es5", "downlevelIteration": true, "importHelpers": true, "lib": [ "dom", "es5", "es2015.collection", "es2015.iterable" ] } }
下面通過編譯器運行後獲得的JS代碼:
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var digits = new Map([ [0, "zero"], [1, "one"], [2, "two"], [3, "three"], [4, "four"], [5, "five"], [6, "six"], [7, "seven"], [8, "eight"], [9, "nine"] ]); function printDigits() { try { for (var digits_1 = tslib_1.__values(digits), digits_1_1 = digits_1.next(); !digits_1_1.done; digits_1_1 = digits_1.next()) { var _a = tslib_1.__read(digits_1_1.value, 2), digit = _a[0], name_1 = _a[1]; console.log(digit + " -> " + name_1); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (digits_1_1 && !digits_1_1.done && (_b = digits_1.return)) _b.call(digits_1); } finally { if (e_1) throw e_1.error; } } var e_1, _b; } exports.printDigits = printDigits;
注意,代碼再也不包含內聯的幫助函數,相反,是從tslib
導入。
在 TypeScript 2.2 以前,類型檢查和錯誤報告只能在.ts
文件中使用。從 TypeScript 2.3 開始,編譯器如今能夠對普通的.js
文件進行類型檢查並報告錯誤。
let foo = 42; // [js] Property 'toUpperCase' does not exist on type 'number'. let upperFoo = foo.toUpperCase();
這裏有一個新的--checkJs
標誌,它默認支持全部.js
文件的類型檢查。另外,三個以註釋形式出現的新指令容許對應該檢查哪些 JS 代碼片斷進行更細粒度的控制:
// @ ts-check
註釋對單個文件的類型檢查。// @ts-nocheck
註釋來跳過對某些文件的檢查// @ ts-ignore
註釋爲單行選擇不進行類型檢查。這些選項使我們可使用黑名單方法和白名單方法。請注意,不管哪一種方式,都應將--allowJs
選項設置爲true,以便首先容許在編譯中包含 JS 文件。
黑名單方法背後的實現方式是默認狀況下對每一個 JS 文件進行類型檢查。這能夠經過將--checkJs
編譯器選項設置爲true
來實現。也能夠經過在每一個文件的頂部添加// @ ts-nocheck
註釋來將特定文件列入黑名單。
若是你想要一次檢查一下 JS 代碼庫,則建議使用這種方法。若是報告了錯誤,則能夠當即修復它,使用// @ ts-ignore
忽略致使錯誤的行,或使用// @ ts-nocheck
忽略整個文件。
白名單方法背後的實現方式是默認狀況下只對選定的 JS 文件進行類型檢查。這能夠經過將- checkJs
編譯器選項設置爲false
並在每一個選定文件的頂部添加// @ts-check
註釋來實現。
若是你想要在大型 JS代碼庫中逐步引入類型檢查,推薦這種方法。這樣,將不會一次被太多錯誤淹沒。每當在處理文件時,請考慮先添加// @ ts-check
並修復潛在的類型錯誤,以有效地實現蠕變遷移。
一旦對整個代碼庫進行了類型檢查,從 JS (和.js
文件)遷移到 TypeScript (和.ts文件
)就容易多了。使用白名單或黑名單方法,我們能夠很快的移到,同時準備遷移到徹底靜態類型的代碼庫(由TypeScript提供支持)。
代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug。
原文:
https://mariusschulz.com/blog...
https://mariusschulz.com/blog...
https://www.tslang.cn/docs/re...
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
https://github.com/qq44924588...
我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!
關注公衆號,後臺回覆福利,便可看到福利,你懂的。