做者: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...
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。