From e4c267e217dedc8096f44bc998c3ab4ebb58b978 Mon Sep 17 00:00:00 2001 From: JeremyFan Date: Thu, 16 Mar 2017 15:00:49 +0800 Subject: [PATCH 001/768] typo fix --- docs/module-loader.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index 022860eed..036f9c01b 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -740,7 +740,7 @@ TypeError: even is not a function [ES6 module transpiler](https://github.com/esnext/es6-module-transpiler)是 square 公司开源的一个转码器,可以将 ES6 模块转为 CommonJS 模块或 AMD 模块的写法,从而在浏览器中使用。 -首先,安装这个转玛器。 +首先,安装这个转码器。 ```bash $ npm install -g es6-module-transpiler From f56ab65469ed41c0c7459393bff50daf2c4ff604 Mon Sep 17 00:00:00 2001 From: duzeng Date: Sat, 18 Mar 2017 00:23:12 +0800 Subject: [PATCH 002/768] docs(promise): edit promise.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 就算改变已经发生了,你再对`Promise`对象添加回调函数--------->如果改变已经发生了,就算你再对`Promise`对象添加回调函数 貌似这样更能表达原意。 --- docs/promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/promise.md b/docs/promise.md index 283144ada..55dbfdf5e 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -10,7 +10,7 @@ Promise 是异步编程的一种解决方案,比传统的解决方案——回 (1)对象的状态不受外界影响。`Promise`对象代表一个异步操作,有三种状态:`Pending`(进行中)、`Resolved`(已完成,又称 Fulfilled)和`Rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是`Promise`这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 -(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。`Promise`对象的状态改变,只有两种可能:从`Pending`变为`Resolved`和从`Pending`变为`Rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对`Promise`对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 +(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。`Promise`对象的状态改变,只有两种可能:从`Pending`变为`Resolved`和从`Pending`变为`Rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,就算你再对`Promise`对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 有了`Promise`对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,`Promise`对象提供统一的接口,使得控制异步操作更加容易。 From 2c4d545dccb1630db60f162513927220eb9c9bb5 Mon Sep 17 00:00:00 2001 From: Willin Wang Date: Thu, 23 Mar 2017 11:31:50 +0800 Subject: [PATCH 003/768] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20async?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这里不需要 `async` 去掉更容易理解其原理。 --- docs/async.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/async.md b/docs/async.md index a5c8c5e8f..de1bc598d 100644 --- a/docs/async.md +++ b/docs/async.md @@ -376,8 +376,8 @@ async function dbFuc(db) { 上面代码会报错,因为`await`用在普通函数之中了。但是,如果将`forEach`方法的参数改成`async`函数,也有问题。 ```javascript -async function dbFuc(db) { - let docs = [{}, {}, {}]; +function dbFuc(db) { //这里不需要 async +  let docs = [{}, {}, {}]; // 可能得到错误结果 docs.forEach(async function (doc) { From 5a34395c74ef86a2c889e2e38a920e045c2511fb Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 27 Mar 2017 07:09:55 +0800 Subject: [PATCH 004/768] docs(intro): edit intro/babel --- docs/intro.md | 176 ++++++++++++++++-------------------------------- docs/promise.md | 2 +- 2 files changed, 58 insertions(+), 120 deletions(-) diff --git a/docs/intro.md b/docs/intro.md index f3d5dec25..e6c9c1403 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -50,80 +50,37 @@ ES6 从开始制定到最后发布,整整用了15年。 前面提到,ECMAScript 1.0是1997年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998年6月)和 ECMAScript 3.0(1999年12月)。3.0版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习 JavaScript,其实就是在学3.0版的语法。 -2000年,ECMAScript 4.0开始酝酿。这个版本最后没有通过,但是它的大部分内容被ES6继承了。因此,ES6制定的起点其实是2000年。 +2000年,ECMAScript 4.0开始酝酿。这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是2000年。 -为什么ES4没有通过呢?因为这个版本太激进了,对ES3做了彻底升级,导致标准委员会的一些成员不愿意接受。ECMA的第39号技术专家委员会(Technical Committee 39,简称TC39)负责制订ECMAScript标准,成员包括Microsoft、Mozilla、Google等大公司。 +为什么 ES4 没有通过呢?因为这个版本太激进了,对 ES3 做了彻底升级,导致标准委员会的一些成员不愿意接受。ECMA 的第39号技术专家委员会(Technical Committee 39,简称TC39)负责制订 ECMAScript 标准,成员包括 Microsoft、Mozilla、Google 等大公司。 -2007年10月,ECMAScript 4.0版草案发布,本来预计次年8月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以Yahoo、Microsoft、Google为首的大公司,反对JavaScript的大幅升级,主张小幅改动;以JavaScript创造者Brendan Eich为首的Mozilla公司,则坚持当前的草案。 +2007年10月,ECMAScript 4.0 版草案发布,本来预计次年8月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以 Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动;以 JavaScript 创造者Brendan Eich为首的Mozilla公司,则坚持当前的草案。 -2008年7月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA开会决定,中止ECMAScript 4.0的开发,将其中涉及现有功能改善的一小部分,发布为ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为Harmony(和谐)。会后不久,ECMAScript 3.1就改名为ECMAScript 5。 +2008年7月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA 开会决定,中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分,发布为 ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为 Harmony(和谐)。会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。 -2009年12月,ECMAScript 5.0版正式发布。Harmony项目则一分为二,一些较为可行的设想定名为JavaScript.next继续开发,后来演变成ECMAScript 6;一些不是很成熟的设想,则被视为JavaScript.next.next,在更远的将来再考虑推出。TC39委员会的总体考虑是,ES5与ES3基本保持兼容,较大的语法修正和新功能加入,将由JavaScript.next完成。当时,JavaScript.next指的是ES6,第六版发布以后,就指ES7。TC39的判断是,ES5会在2013年的年中成为JavaScript开发的主流标准,并在此后五年中一直保持这个位置。 +2009年12月,ECMAScript 5.0 版正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。TC39 委员会的总体考虑是,ES5 与 ES3 基本保持兼容,较大的语法修正和新功能加入,将由 JavaScript.next 完成。当时,JavaScript.next 指的是ES6,第六版发布以后,就指 ES7。TC39 的判断是,ES5 会在2013年的年中成为 JavaScript 开发的主流标准,并在此后五年中一直保持这个位置。 -2011年6月,ECMAscript 5.1版发布,并且成为ISO国际标准(ISO/IEC 16262:2011)。 +2011年6月,ECMAscript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。 -2013年3月,ECMAScript 6草案冻结,不再添加新功能。新的功能设想将被放到ECMAScript 7。 +2013年3月,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript 7。 -2013年12月,ECMAScript 6草案发布。然后是12个月的讨论期,听取各方反馈。 +2013年12月,ECMAScript 6 草案发布。然后是12个月的讨论期,听取各方反馈。 -2015年6月,ECMAScript 6正式通过,成为国际标准。从2000年算起,这时已经过去了15年。 +2015年6月,ECMAScript 6 正式通过,成为国际标准。从2000年算起,这时已经过去了15年。 ## 部署进度 -各大浏览器的最新版本,对ES6的支持可以查看[kangax.github.io/es5-compat-table/es6/](http://kangax.github.io/es5-compat-table/es6/)。随着时间的推移,支持度已经越来越高了,ES6的大部分特性都实现了。 +各大浏览器的最新版本,对 ES6 的支持可以查看[kangax.github.io/es5-compat-table/es6/](http://kangax.github.io/es5-compat-table/es6/)。随着时间的推移,支持度已经越来越高了,超过90%的 ES6 语法特性都实现了。 -Node.js是JavaScript语言的服务器运行环境,对ES6的支持度比浏览器更高。通过Node,可以体验更多ES6的特性。建议使用版本管理工具[nvm](https://github.com/creationix/nvm),来安装Node,因为可以自由切换版本。不过,`nvm`不支持Windows系统,如果你使用Windows系统,下面的操作可以改用[nvmw](https://github.com/hakobera/nvmw)或[nvm-windows](https://github.com/coreybutler/nvm-windows)代替。 - -安装nvm需要打开命令行窗口,运行下面的命令。 - -```bash -$ curl -o- https://raw.githubusercontent.com/creationix/nvm//install.sh | bash -``` - -上面命令的`version number`处,需要用版本号替换。本节写作时的版本号是`v0.29.0`。该命令运行后,`nvm`会默认安装在用户主目录的`.nvm`子目录。 - -然后,激活`nvm`。 - -```bash -$ source ~/.nvm/nvm.sh -``` - -激活以后,安装Node的最新版。 - -```bash -$ nvm install node -``` - -安装完成后,切换到该版本。 - -```bash -$ nvm use node -``` - -使用下面的命令,可以查看Node所有已经实现的ES6特性。 +Node 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node 那些没有打开的 ES6 特性。 ```bash $ node --v8-options | grep harmony - - --harmony_typeof - --harmony_scoping - --harmony_modules - --harmony_symbols - --harmony_proxies - --harmony_collections - --harmony_observation - --harmony_generators - --harmony_iteration - --harmony_numeric_literals - --harmony_strings - --harmony_arrays - --harmony_maths - --harmony ``` 上面命令的输出结果,会因为版本的不同而有所不同。 -我写了一个[ES-Checker](https://github.com/ruanyf/es-checker)模块,用来检查各种运行环境对ES6的支持情况。访问[ruanyf.github.io/es-checker](http://ruanyf.github.io/es-checker),可以看到您的浏览器支持ES6的程度。运行下面的命令,可以查看你正在使用的Node环境对ES6的支持程度。 +我写了一个工具 [ES-Checker](https://github.com/ruanyf/es-checker),用来检查各种运行环境对 ES6 的支持情况。访问[ruanyf.github.io/es-checker](http://ruanyf.github.io/es-checker),可以看到您的浏览器支持 ES6 的程度。运行下面的命令,可以查看你正在使用的 Node 环境对 ES6 的支持程度。 ```bash $ npm install -g es-checker @@ -135,9 +92,9 @@ Your runtime supports 57% of ECMAScript 6 ========================================= ``` -## Babel转码器 +## Babel 转码器 -[Babel](https://babeljs.io/)是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。 +[Babel](https://babeljs.io/) 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在现有环境执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。 ```javascript // 转码前 @@ -149,11 +106,11 @@ input.map(function (item) { }); ``` -上面的原始代码用了箭头函数,这个特性还没有得到广泛支持,Babel将其转为普通函数,就能在现有的JavaScript环境执行了。 +上面的原始代码用了箭头函数,Babel 将其转为普通函数,就能在不支持箭头函数的 JavaScript 环境执行了。 ### 配置文件`.babelrc` -Babel的配置文件是`.babelrc`,存放在项目的根目录下。使用Babel的第一步,就是配置这个文件。 +Babel 的配置文件是`.babelrc`,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。 该文件用来设置转码规则和插件,基本格式如下。 @@ -167,13 +124,13 @@ Babel的配置文件是`.babelrc`,存放在项目的根目录下。使用Babel `presets`字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。 ```bash -# ES2015转码规则 -$ npm install --save-dev babel-preset-es2015 +# 最新转码规则 +$ npm install --save-dev babel-preset-latest -# react转码规则 +# react 转码规则 $ npm install --save-dev babel-preset-react -# ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个 +# 不同阶段语法提案的转码规则(共有4个阶段),选装一个 $ npm install --save-dev babel-preset-stage-0 $ npm install --save-dev babel-preset-stage-1 $ npm install --save-dev babel-preset-stage-2 @@ -185,7 +142,7 @@ $ npm install --save-dev babel-preset-stage-3 ```javascript { "presets": [ - "es2015", + "latest", "react", "stage-2" ], @@ -193,7 +150,7 @@ $ npm install --save-dev babel-preset-stage-3 } ``` -注意,以下所有Babel工具和模块的使用,都必须先写好`.babelrc`。 +注意,以下所有 Babe l工具和模块的使用,都必须先写好`.babelrc`。 ### 命令行转码`babel-cli` @@ -227,7 +184,7 @@ $ babel src -d lib $ babel src -d lib -s ``` -上面代码是在全局环境下,进行Babel转码。这意味着,如果项目要运行,全局环境必须有Babel,也就是说项目产生了对环境的依赖。另一方面,这样做也无法支持不同项目使用不同版本的Babel。 +上面代码是在全局环境下,进行 Babel 转码。这意味着,如果项目要运行,全局环境必须有 Babel,也就是说项目产生了对环境的依赖。另一方面,这样做也无法支持不同项目使用不同版本的 Babel。 一个解决办法是将`babel-cli`安装在项目之中。 @@ -314,7 +271,7 @@ require("./index.js"); ### babel-core -如果某些代码需要调用Babel的API进行转码,就要使用`babel-core`模块。 +如果某些代码需要调用 Babel 的 API 进行转码,就要使用`babel-core`模块。 安装命令如下。 @@ -353,7 +310,7 @@ babel.transformFromAst(ast, code, options); var es6Code = 'let x = n => n + 1'; var es5Code = require('babel-core') .transform(es6Code, { - presets: ['es2015'] + presets: ['latest'] }) .code; // '"use strict";\n\nvar x = function x(n) {\n return n + 1;\n};' @@ -363,9 +320,9 @@ var es5Code = require('babel-core') ### babel-polyfill -Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如`Object.assign`)都不会转码。 +Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如`Iterator`、`Generator`、`Set`、`Maps`、`Proxy`、`Reflect`、`Symbol`、`Promise`等全局对象,以及一些定义在全局对象上的方法(比如`Object.assign`)都不会转码。 -举例来说,ES6在`Array`对象上新增了`Array.from`方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用`babel-polyfill`,为当前环境提供一个垫片。 +举例来说,ES6在`Array`对象上新增了`Array.from`方法。Babel 就不会转码这个方法。如果想让这个方法运行,必须使用`babel-polyfill`,为当前环境提供一个垫片。 安装命令如下。 @@ -381,30 +338,11 @@ import 'babel-polyfill'; require('babel-polyfill'); ``` -Babel默认不转码的API非常多,详细清单可以查看`babel-plugin-transform-runtime`模块的[definitions.js](https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-runtime/src/definitions.js)文件。 +Babel 默认不转码的 API 非常多,详细清单可以查看`babel-plugin-transform-runtime`模块的[definitions.js](https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-runtime/src/definitions.js)文件。 ### 浏览器环境 -Babel也可以用于浏览器环境。但是,从Babel 6.0开始,不再直接提供浏览器版本,而是要用构建工具构建出来。如果你没有或不想使用构建工具,可以通过安装5.x版本的`babel-core`模块获取。 - -```bash -$ npm install babel-core@5 -``` - -运行上面的命令以后,就可以在当前目录的`node_modules/babel-core/`子目录里面,找到`babel`的浏览器版本`browser.js`(未精简)和`browser.min.js`(已精简)。 - -然后,将下面的代码插入网页。 - -```html - - -``` - -上面代码中,`browser.js`是Babel提供的转换器脚本,可以在浏览器运行。用户的ES6脚本放在`script`标签之中,但是要注明`type="text/babel"`。 - -另一种方法是使用[babel-standalone](https://github.com/Daniel15/babel-standalone)模块提供的浏览器版本,将其插入网页。 +Babel 也可以用于浏览器环境。但是,从 Babel 6.0 开始,不再直接提供浏览器版本,而是要用构建工具构建出来。如果你没有或不想使用构建工具,可以使用[babel-standalone](https://github.com/Daniel15/babel-standalone)模块提供的浏览器版本,将其插入网页。 ```html @@ -413,19 +351,19 @@ $ npm install babel-core@5 ``` -注意,网页中实时将ES6代码转为ES5,对性能会有影响。生产环境需要加载已经转码完成的脚本。 +注意,网页实时将 ES6 代码转为 ES5,对性能会有影响。生产环境需要加载已经转码完成的脚本。 下面是如何将代码打包成浏览器可以使用的脚本,以`Babel`配合`Browserify`为例。首先,安装`babelify`模块。 ```bash -$ npm install --save-dev babelify babel-preset-es2015 +$ npm install --save-dev babelify babel-preset-latest ``` -然后,再用命令行转换ES6脚本。 +然后,再用命令行转换 ES6 脚本。 ```bash $ browserify script.js -o bundle.js \ - -t [ babelify --presets [ es2015 ] ] + -t [ babelify --presets [ latest ] ] ``` 上面代码将ES6脚本`script.js`,转为`bundle.js`,浏览器直接加载后者就可以了。 @@ -435,20 +373,20 @@ $ browserify script.js -o bundle.js \ ```javascript { "browserify": { - "transform": [["babelify", { "presets": ["es2015"] }]] + "transform": [["babelify", { "presets": ["latest"] }]] } } ``` ### 在线转换 -Babel提供一个[REPL在线编译器](https://babeljs.io/repl/),可以在线将ES6代码转为ES5代码。转换后的代码,可以直接作为ES5代码插入网页运行。 +Babel 提供一个[REPL在线编译器](https://babeljs.io/repl/),可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。 ### 与其他工具的配合 -许多工具需要Babel进行前置转码,这里举两个例子:ESLint和Mocha。 +许多工具需要 Babel 进行前置转码,这里举两个例子:ESLint 和 Mocha。 -ESLint用于静态检查代码的语法和风格,安装命令如下。 +ESLint 用于静态检查代码的语法和风格,安装命令如下。 ```bash $ npm install --save-dev eslint babel-eslint @@ -480,7 +418,7 @@ $ npm install --save-dev eslint babel-eslint } ``` -Mocha则是一个测试框架,如果需要执行使用ES6语法的测试脚本,可以修改`package.json`的`scripts.test`。 +Mocha 则是一个测试框架,如果需要执行使用 ES6 语法的测试脚本,可以修改`package.json`的`scripts.test`。 ```javascript "scripts": { @@ -490,13 +428,13 @@ Mocha则是一个测试框架,如果需要执行使用ES6语法的测试脚本 上面命令中,`--compilers`参数指定脚本的转码器,规定后缀名为`js`的文件,都需要使用`babel-core/register`先转码。 -## Traceur转码器 +## Traceur 转码器 -Google公司的[Traceur](https://github.com/google/traceur-compiler)转码器,也可以将ES6代码转为ES5代码。 +Google公司的[Traceur](https://github.com/google/traceur-compiler)转码器,也可以将 ES6 代码转为 ES5 代码。 ### 直接插入网页 -Traceur允许将ES6代码直接插入网页。首先,必须在网页头部加载Traceur库文件。 +Traceur 允许将 ES6 代码直接插入网页。首先,必须在网页头部加载 Traceur 库文件。 ```html @@ -507,19 +445,19 @@ Traceur允许将ES6代码直接插入网页。首先,必须在网页头部加 ``` -上面代码中,一共有4个`script`标签。第一个是加载Traceur的库文件,第二个和第三个是将这个库文件用于浏览器环境,第四个则是加载用户脚本,这个脚本里面可以使用ES6代码。 +上面代码中,一共有4个`script`标签。第一个是加载 Traceur 的库文件,第二个和第三个是将这个库文件用于浏览器环境,第四个则是加载用户脚本,这个脚本里面可以使用ES6代码。 -注意,第四个`script`标签的`type`属性的值是`module`,而不是`text/javascript`。这是Traceur编译器识别ES6代码的标志,编译器会自动将所有`type=module`的代码编译为ES5,然后再交给浏览器执行。 +注意,第四个`script`标签的`type`属性的值是`module`,而不是`text/javascript`。这是 Traceur 编译器识别 ES6 代码的标志,编译器会自动将所有`type=module`的代码编译为 ES5,然后再交给浏览器执行。 -除了引用外部ES6脚本,也可以直接在网页中放置ES6代码。 +除了引用外部 ES6 脚本,也可以直接在网页中放置 ES6 代码。 ```javascript ``` -正常情况下,上面代码会在控制台打印出9。 +正常情况下,上面代码会在控制台打印出`9`。 -如果想对Traceur的行为有精确控制,可以采用下面参数配置的写法。 +如果想对 Traceur 的行为有精确控制,可以采用下面参数配置的写法。 ```javascript ``` -上面代码中,首先生成Traceur的全局对象`window.System`,然后`System.import`方法可以用来加载ES6模块。加载的时候,需要传入一个配置对象`metadata`,该对象的`traceurOptions`属性可以配置支持ES6功能。如果设为`experimental: true`,就表示除了ES6以外,还支持一些实验性的新功能。 +上面代码中,首先生成Traceur的全局对象`window.System`,然后`System.import`方法可以用来加载 ES6。加载的时候,需要传入一个配置对象`metadata`,该对象的`traceurOptions`属性可以配置支持 ES6 功能。如果设为`experimental: true`,就表示除了 ES6 以外,还支持一些实验性的新功能。 ### 在线转换 -Traceur也提供一个[在线编译器](http://google.github.io/traceur-compiler/demo/repl.html),可以在线将ES6代码转为ES5代码。转换后的代码,可以直接作为ES5代码插入网页运行。 +Traceur也提供一个[在线编译器](http://google.github.io/traceur-compiler/demo/repl.html),可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。 -上面的例子转为ES5代码运行,就是下面这个样子。 +上面的例子转为 ES5 代码运行,就是下面这个样子。 ```javascript @@ -590,15 +528,15 @@ $traceurRuntime.ModuleStore.getAnonymousModule(function() { ### 命令行转换 -作为命令行工具使用时,Traceur是一个Node的模块,首先需要用Npm安装。 +作为命令行工具使用时,Traceur 是一个 Node 的模块,首先需要用 Npm 安装。 ```bash $ npm install -g traceur ``` -安装成功后,就可以在命令行下使用Traceur了。 +安装成功后,就可以在命令行下使用 Traceur 了。 -Traceur直接运行es6脚本文件,会在标准输出显示运行结果,以前面的`calc.js`为例。 +Traceur 直接运行 ES6 脚本文件,会在标准输出显示运行结果,以前面的`calc.js`为例。 ```bash $ traceur calc.js @@ -606,7 +544,7 @@ Calc constructor 9 ``` -如果要将ES6脚本转为ES5保存,要采用下面的写法。 +如果要将 ES6 脚本转为 ES5 保存,要采用下面的写法。 ```bash $ traceur --script calc.es6.js --out calc.es5.js @@ -622,9 +560,9 @@ $ traceur --script calc.es6.js --out calc.es5.js --experimental 命令行下转换生成的文件,就可以直接放到浏览器中运行。 -### Node.js环境的用法 +### Node 环境的用法 -Traceur的Node.js用法如下(假定已安装traceur模块)。 +Traceur 的 Node用法如下(假定已安装`traceur`模块)。 ```javascript var traceur = require('traceur'); diff --git a/docs/promise.md b/docs/promise.md index 55dbfdf5e..256524198 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -10,7 +10,7 @@ Promise 是异步编程的一种解决方案,比传统的解决方案——回 (1)对象的状态不受外界影响。`Promise`对象代表一个异步操作,有三种状态:`Pending`(进行中)、`Resolved`(已完成,又称 Fulfilled)和`Rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是`Promise`这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 -(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。`Promise`对象的状态改变,只有两种可能:从`Pending`变为`Resolved`和从`Pending`变为`Rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,就算你再对`Promise`对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 +(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。`Promise`对象的状态改变,只有两种可能:从`Pending`变为`Resolved`和从`Pending`变为`Rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对`Promise`对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 有了`Promise`对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,`Promise`对象提供统一的接口,使得控制异步操作更加容易。 From 3619d2121ca0ba7506f1f0f909f0f5dc76d60dcf Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 27 Mar 2017 12:45:33 +0800 Subject: [PATCH 005/768] docs(proxy): edit proxy --- docs/proxy.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index 790bd6adc..57014cba1 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -637,7 +637,7 @@ proxy.foo = 'bar' ### getOwnPropertyDescriptor() -`getOwnPropertyDescriptor`方法拦截`Object.getOwnPropertyDescriptor`,返回一个属性描述对象或者`undefined`。 +`getOwnPropertyDescriptor`方法拦截`Object.getOwnPropertyDescriptor()`,返回一个属性描述对象或者`undefined`。 ```javascript var handler = { @@ -662,13 +662,13 @@ Object.getOwnPropertyDescriptor(proxy, 'baz') ### getPrototypeOf() -`getPrototypeOf`方法主要用来拦截`Object.getPrototypeOf()`运算符,以及其他一些操作。 +`getPrototypeOf`方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。 - `Object.prototype.__proto__` - `Object.prototype.isPrototypeOf()` - `Object.getPrototypeOf()` - `Reflect.getPrototypeOf()` -- `instanceof`运算符 +- `instanceof` 下面是一个例子。 @@ -727,7 +727,7 @@ Object.isExtensible(p) // 报错 ### ownKeys() -`ownKeys`方法用来拦截以下操作。 +`ownKeys`方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。 - `Object.getOwnPropertyNames()` - `Object.getOwnPropertySymbols()` From 0fb4fcd9416f8c1bc81a342e73b07e232b25be3c Mon Sep 17 00:00:00 2001 From: "Arvin.He" <510205617@qq.com> Date: Tue, 28 Mar 2017 11:38:31 +0800 Subject: [PATCH 006/768] Update number.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "而这两个新方法只对数值有效,非数值一律返回false。" ,这句话对我来说有误导倾向.并非抠字眼,遇到了就顺便提出了. --- docs/number.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/number.md b/docs/number.md index ea152a1b6..e4e72eabf 100644 --- a/docs/number.md +++ b/docs/number.md @@ -94,7 +94,7 @@ ES5通过下面的代码,部署`Number.isNaN()`。 })(this); ``` -它们与传统的全局方法`isFinite()`和`isNaN()`的区别在于,传统方法先调用`Number()`将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,非数值一律返回`false`。 +它们与传统的全局方法`isFinite()`和`isNaN()`的区别在于,传统方法先调用`Number()`将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,isFinite对于非数值一律返回`false`, isNaN只有对于NaN才返回`true`,非NaN一律返回`false`。 ```javascript isFinite(25) // true @@ -106,6 +106,7 @@ isNaN(NaN) // true isNaN("NaN") // true Number.isNaN(NaN) // true Number.isNaN("NaN") // false +Number.isNaN(1) // false ``` ## Number.parseInt(), Number.parseFloat() From b8959b2e6d63acc7671cfc8beefae8df7208e91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=81=A5=E8=B6=85?= <574805242@qq.com> Date: Tue, 28 Mar 2017 19:34:01 +0800 Subject: [PATCH 007/768] Update object.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 错别字:“预算”->“运算” --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index 8ac6d75d3..62b53996c 100644 --- a/docs/object.md +++ b/docs/object.md @@ -991,7 +991,7 @@ function entries(obj) { ## 对象的扩展运算符 -《数组的扩展》一章中,已经介绍过扩展预算符(`...`)。 +《数组的扩展》一章中,已经介绍过扩展运算符(`...`)。 ```javascript const [a, ...b] = [1, 2, 3]; From d51beedbd61a99ee310676b62d2fa1bba820afb8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 28 Mar 2017 19:46:30 +0800 Subject: [PATCH 008/768] docs: edit number.isFinite --- docs/async.md | 41 +++++++++++++++++++++++++++++++++++++- docs/generator-async.md | 44 ++++++++++++++++++++++++++++++++++++++--- docs/intro.md | 6 +++--- docs/number.md | 2 +- docs/reflect.md | 26 ++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 8 deletions(-) diff --git a/docs/async.md b/docs/async.md index de1bc598d..ce5885ef9 100644 --- a/docs/async.md +++ b/docs/async.md @@ -100,7 +100,7 @@ function timeout(ms) { async function asyncPrint(value, ms) { await timeout(ms); - console.log(value) + console.log(value); } asyncPrint('hello world', 50); @@ -108,6 +108,23 @@ asyncPrint('hello world', 50); 上面代码指定50毫秒以后,输出`hello world`。 +由于`async`函数返回的是 Promise 对象,可以作为`await`命令的参数。所以,上面的例子也可以写成下面的形式。 + +```javascript +async function timeout(ms) { + await new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +async function asyncPrint(value, ms) { + await timeout(ms); + console.log(value); +} + +asyncPrint('hello world', 50); +``` + async 函数有多种使用形式。 ```javascript @@ -315,6 +332,28 @@ async function main() { } ``` +下面的例子使用`try...catch`结构,实现多次重复尝试。 + +```javascript +const superagent = require('superagent'); +const NUM_RETRIES = 3; + +async function test() { + let i; + for (i = 0; i < NUM_RETRIES; ++i) { + try { + await superagent.get('http://google.com/this-throws-an-error'); + break; + } catch(err) {} + } + console.log(i); // 3 +} + +test(); +``` + +上面代码中,如果`await`操作成功,就会使用`break`语句退出循环;如果失败,会被`catch`语句捕捉,然后进入下一轮循环。 + ### 使用注意点 第一点,前面已经说过,`await`命令后面的`Promise`对象,运行结果可能是`rejected`,所以最好把`await`命令放在`try...catch`代码块中。 diff --git a/docs/generator-async.md b/docs/generator-async.md index 79939971c..4018cd141 100644 --- a/docs/generator-async.md +++ b/docs/generator-async.md @@ -544,9 +544,9 @@ var co = require('co'); co(gen); ``` -上面代码中,Generator函数只要传入`co`函数,就会自动执行。 +上面代码中,Generator 函数只要传入`co`函数,就会自动执行。 -`co`函数返回一个Promise对象,因此可以用`then`方法添加回调函数。 +`co`函数返回一个`Promise`对象,因此可以用`then`方法添加回调函数。 ```javascript co(gen).then(function (){ @@ -568,7 +568,7 @@ co(gen).then(function (){ (2)Promise 对象。将异步操作包装成 Promise 对象,用`then`方法交回执行权。 -co 模块其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个模块。使用 co 的前提条件是,Generator 函数的`yield`命令后面,只能是 Thunk 函数或 Promise 对象。如果数组或对象的成员,全部都是 Promise 对象,也是可以的,详见后文的例子。(4.0 版以后,`yield`命令后面只能是 Promise 对象。) +co 模块其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个模块。使用 co 的前提条件是,Generator 函数的`yield`命令后面,只能是 Thunk 函数或 Promise 对象。如果数组或对象的成员,全部都是 Promise 对象,也可以使用 co,详见后文的例子。(co v4.0版以后,`yield`命令后面只能是 Promise 对象,不再支持 Thunk 函数。) 上一节已经介绍了基于 Thunk 函数的自动执行器。下面来看,基于 Promise 对象的自动执行器。这是理解 co 模块必须的。 @@ -752,3 +752,41 @@ function* somethingAsync(x) { 上面的代码允许并发三个`somethingAsync`异步操作,等到它们全部完成,才会进行下一步。 +### 实例:处理 Stream + +Node 提供 Stream 模式读写数据,特点是一次只处理数据的一部分,数据分成一块块依次处理,就好像“数据流”一样。这对于处理大规模数据非常有利。Stream 模式使用 EventEmitter API,会释放三个事件。 + +- `data`事件:下一块数据块已经准备好了。 +- `end`事件:整个“数据流”处理“完了。 +- `error`事件:发生错误。 + +使用`Promise.race()`函数,可以判断这三个事件之中哪一个最先发生,只有当`data`时间最先发生时,才进入下一个数据块的处理。从而,通过一个`while`循环,完成所有数据的读取。 + +```javascript +const co = require('co'); +const fs = require('fs'); + +const stream = fs.createReadStream('./les_miserables.txt'); +let valjeanCount = 0; + +co(function*() { + while(true) { + const res = yield Promise.race([ + new Promise(resolve => stream.once('data', resolve)), + new Promise(resolve => stream.once('end', resolve)), + new Promise((resolve, reject) => stream.once('error', reject)) + ]); + if (!res) { + break; + } + stream.removeAllListeners('data'); + stream.removeAllListeners('end'); + stream.removeAllListeners('error'); + valjeanCount += (res.toString().match(/valjean/ig) || []).length; + } + console.log('count:', valjeanCount); // count: 1120 +}); +``` + +上面代码采用 Stream 模式读取《悲惨世界》的文本文件,对于每个数据块都使用`stream.once`方法,在`data`、`end`、`error`三个事件上添加一次性回调函数。变量`res`只有在`data`事件发生时,才有值。然后,累加每个数据块之中`valjean`这个词出现的次数。 + diff --git a/docs/intro.md b/docs/intro.md index e6c9c1403..77412e788 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -48,9 +48,9 @@ ES6 的第一个版本,就这样在2015年6月发布了,正式名称就是 ES6 从开始制定到最后发布,整整用了15年。 -前面提到,ECMAScript 1.0是1997年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998年6月)和 ECMAScript 3.0(1999年12月)。3.0版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习 JavaScript,其实就是在学3.0版的语法。 +前面提到,ECMAScript 1.0 是1997年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998年6月)和 ECMAScript 3.0(1999年12月)。3.0版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习 JavaScript,其实就是在学3.0版的语法。 -2000年,ECMAScript 4.0开始酝酿。这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是2000年。 +2000年,ECMAScript 4.0 开始酝酿。这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是2000年。 为什么 ES4 没有通过呢?因为这个版本太激进了,对 ES3 做了彻底升级,导致标准委员会的一些成员不愿意接受。ECMA 的第39号技术专家委员会(Technical Committee 39,简称TC39)负责制订 ECMAScript 标准,成员包括 Microsoft、Mozilla、Google 等大公司。 @@ -72,7 +72,7 @@ ES6 从开始制定到最后发布,整整用了15年。 各大浏览器的最新版本,对 ES6 的支持可以查看[kangax.github.io/es5-compat-table/es6/](http://kangax.github.io/es5-compat-table/es6/)。随着时间的推移,支持度已经越来越高了,超过90%的 ES6 语法特性都实现了。 -Node 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node 那些没有打开的 ES6 特性。 +Node 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持度更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node 已经实现的 ES6 特性。 ```bash $ node --v8-options | grep harmony diff --git a/docs/number.md b/docs/number.md index e4e72eabf..e10c0bda6 100644 --- a/docs/number.md +++ b/docs/number.md @@ -94,7 +94,7 @@ ES5通过下面的代码,部署`Number.isNaN()`。 })(this); ``` -它们与传统的全局方法`isFinite()`和`isNaN()`的区别在于,传统方法先调用`Number()`将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,isFinite对于非数值一律返回`false`, isNaN只有对于NaN才返回`true`,非NaN一律返回`false`。 +它们与传统的全局方法`isFinite()`和`isNaN()`的区别在于,传统方法先调用`Number()`将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,`Number.isFinite()`对于非数值一律返回`false`, `Number.isNaN()`只有对于`NaN`才返回`true`,非`NaN`一律返回`false`。 ```javascript isFinite(25) // true diff --git a/docs/reflect.md b/docs/reflect.md index ed9b028bd..349020144 100644 --- a/docs/reflect.md +++ b/docs/reflect.md @@ -193,6 +193,32 @@ Reflect.set(1, 'foo', {}) // 报错 Reflect.set(false, 'foo', {}) // 报错 ``` +注意,`Reflect.set`会触发`Proxy.defineProperty`拦截。 + +```javascript +let p = { + a: 'a' +}; + +let handler = { + set(target,key,value,receiver) { + console.log('set'); + Reflect.set(target,key,value,receiver) + }, + defineProperty(target, key, attribute) { + console.log('defineProperty'); + Reflect.defineProperty(target,key,attribute); + } +}; + +let obj = new Proxy(p, handler); +obj.a = 'A'; +// set +// defineProperty +``` + +上面代码中,`Proxy.set`拦截中使用了`Reflect.set`,导致触发`Proxy.defineProperty`拦截。 + ### Reflect.has(obj, name) `Reflect.has`方法对应`name in obj`里面的`in`运算符。 From e3cf5bd2251bda741201ae843868f69f09f4e243 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 28 Mar 2017 20:22:04 +0800 Subject: [PATCH 009/768] docs(class): edit class/super --- docs/class.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/class.md b/docs/class.md index 92811f8a6..3904e0779 100644 --- a/docs/class.md +++ b/docs/class.md @@ -777,7 +777,7 @@ class B extends A { 上面代码中,`super()`用在`B`类的`m`方法之中,就会造成句法错误。 -第二种情况,`super`作为对象时,指向父类的原型对象。 +第二种情况,`super`作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。 ```javascript class A { @@ -796,7 +796,7 @@ class B extends A { let b = new B(); ``` -上面代码中,子类`B`当中的`super.p()`,就是将`super`当作一个对象使用。这时,`super`指向`A.prototype`,所以`super.p()`就相当于`A.prototype.p()`。 +上面代码中,子类`B`当中的`super.p()`,就是将`super`当作一个对象使用。这时,`super`在普通方法之中,指向`A.prototype`,所以`super.p()`就相当于`A.prototype.p()`。 这里需要注意,由于`super`指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过`super`调用的。 @@ -889,6 +889,37 @@ let b = new B(); 上面代码中,`super.x`赋值为`3`,这时等同于对`this.x`赋值为`3`。而当读取`super.x`的时候,读的是`A.prototype.x`,所以返回`undefined`。 +如果`super`作为对象,用在静态方法之中,这时`super`将指向父类,而不是父类的原型对象。 + +```javascript +class Parent { + static myMethod(msg) { + console.log('static', msg); + } + + myMethod(msg) { + console.log('instance', msg); + } +} + +class Child extends Parent { + static myMethod(msg) { + super.myMethod(msg); + } + + myMethod(msg) { + super.myMethod(msg); + } +} + +Child.myMethod(1); // static 1 + +var child = new Child(); +child.myMethod(2); // instance 2 +``` + +上面代码中,`super`在静态方法之中指向父类,在普通方法之中指向父类的原型对象。 + 注意,使用`super`的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。 ```javascript From ccefd899e5e5d36443b97614b3993e38ad2a7dcc Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 29 Mar 2017 13:41:41 +0800 Subject: [PATCH 010/768] docs(reflect): edit Reflect.apply --- docs/reflect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reflect.md b/docs/reflect.md index 349020144..7d1d211b8 100644 --- a/docs/reflect.md +++ b/docs/reflect.md @@ -341,7 +341,7 @@ const type = Object.prototype.toString.call(youngest); // 新写法 const youngest = Reflect.apply(Math.min, Math, ages); const oldest = Reflect.apply(Math.max, Math, ages); -const type = Reflect.apply(Object.prototype.toString, youngest); +const type = Reflect.apply(Object.prototype.toString, youngest, []); ``` ### Reflect.defineProperty(target, propertyKey, attributes) From cd360410cfa3343484fe669e973ea13f8af8b7bf Mon Sep 17 00:00:00 2001 From: Toxichl <472590061@qq.com> Date: Wed, 29 Mar 2017 14:46:42 +0800 Subject: [PATCH 011/768] The position of the asterisk is incorrect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combined with the previous content: ``` 由于Generator函数仍然是普通函数,所以一般的写法是上面的第三种,即星号紧跟在`function`关键字后面。**本书也采用这种写法**。 ``` so, the position of the asterisk is incorrect ... --- docs/generator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generator.md b/docs/generator.md index b2b388aaf..aa44375f5 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -585,7 +585,7 @@ try { Generator函数体外抛出的错误,可以在函数体内捕获;反过来,Generator函数体内抛出的错误,也可以被函数体外的`catch`捕获。 ```javascript -function *foo() { +function* foo() { var x = yield 3; var y = x.toUpperCase(); yield y; From 7e2b0c709f4bd1f78304d3a6d9795e443d16be49 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 29 Mar 2017 14:53:21 +0800 Subject: [PATCH 012/768] docs(number): edit number --- css/app.css | 1 + docs/number.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/css/app.css b/css/app.css index fe21dc7b4..a2e2f3c51 100644 --- a/css/app.css +++ b/css/app.css @@ -120,6 +120,7 @@ input[type=search] { height: 18px; text-align: left; border: none; + outline: none; } input.searchButton { diff --git a/docs/number.md b/docs/number.md index e10c0bda6..3a262a545 100644 --- a/docs/number.md +++ b/docs/number.md @@ -658,11 +658,11 @@ ES2016 新增了一个指数运算符(`**`)。 指数运算符可以与等号结合,形成一个新的赋值运算符(`**=`)。 ```javascript -let a = 2; +let a = 1.5; a **= 2; // 等同于 a = a * a; -let b = 3; +let b = 4; b **= 3; // 等同于 b = b * b * b; ``` From 87c0669a0bb9aeca272934b0162293407d92a977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AF=B7=E5=8F=AB=E6=88=91=E7=8E=8B=E7=A3=8A=E5=90=8C?= =?UTF-8?q?=E5=AD=A6=28Wanglei=29?= Date: Sat, 1 Apr 2017 17:17:44 +0800 Subject: [PATCH 013/768] update generator-async.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘next法返回值的value属性’改为‘next返回值的value属性’ --- docs/generator-async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generator-async.md b/docs/generator-async.md index 4018cd141..510113f43 100644 --- a/docs/generator-async.md +++ b/docs/generator-async.md @@ -138,7 +138,7 @@ g.next() // { value: undefined, done: true } Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。 -`next`法返回值的value属性,是 Generator 函数向外输出数据;`next`方法还可以接受参数,向 Generator 函数体内输入数据。 +`next`返回值的value属性,是 Generator 函数向外输出数据;`next`方法还可以接受参数,向 Generator 函数体内输入数据。 ```javascript function* gen(x){ From ea23aa7291536d22c8eb2dd14529f32cfdfeb43c Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 6 Apr 2017 17:45:05 +0800 Subject: [PATCH 014/768] docs(generator): edit generator-async --- docs/generator-async.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/generator-async.md b/docs/generator-async.md index 4018cd141..bb2dc48b4 100644 --- a/docs/generator-async.md +++ b/docs/generator-async.md @@ -760,7 +760,7 @@ Node 提供 Stream 模式读写数据,特点是一次只处理数据的一部 - `end`事件:整个“数据流”处理“完了。 - `error`事件:发生错误。 -使用`Promise.race()`函数,可以判断这三个事件之中哪一个最先发生,只有当`data`时间最先发生时,才进入下一个数据块的处理。从而,通过一个`while`循环,完成所有数据的读取。 +使用`Promise.race()`函数,可以判断这三个事件之中哪一个最先发生,只有当`data`事件最先发生时,才进入下一个数据块的处理。从而,我们可以通过一个`while`循环,完成所有数据的读取。 ```javascript const co = require('co'); @@ -788,5 +788,5 @@ co(function*() { }); ``` -上面代码采用 Stream 模式读取《悲惨世界》的文本文件,对于每个数据块都使用`stream.once`方法,在`data`、`end`、`error`三个事件上添加一次性回调函数。变量`res`只有在`data`事件发生时,才有值。然后,累加每个数据块之中`valjean`这个词出现的次数。 +上面代码采用 Stream 模式读取《悲惨世界》的文本文件,对于每个数据块都使用`stream.once`方法,在`data`、`end`、`error`三个事件上添加一次性回调函数。变量`res`只有在`data`事件发生时才有值,然后累加每个数据块之中`valjean`这个词出现的次数。 From 6975897049bdf0b477534caf9870b04983597ff5 Mon Sep 17 00:00:00 2001 From: Jacty Date: Mon, 10 Apr 2017 01:49:05 +0800 Subject: [PATCH 015/768] type mistake type mistake --- docs/intro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.md b/docs/intro.md index 77412e788..fe7a71826 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -150,7 +150,7 @@ $ npm install --save-dev babel-preset-stage-3 } ``` -注意,以下所有 Babe l工具和模块的使用,都必须先写好`.babelrc`。 +注意,以下所有 Babel工具和模块的使用,都必须先写好`.babelrc`。 ### 命令行转码`babel-cli` From 0fbc3306bea2f04c35e72139a898615bc61cc877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=90=E9=9C=96?= <304647173@qq.com> Date: Tue, 11 Apr 2017 13:07:06 +0800 Subject: [PATCH 016/768] =?UTF-8?q?=E6=9B=B4=E6=AD=A3=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generator函数的`next`方法输出的内容顺序一般是`value`在前,`done`在后。 --- docs/generator.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index aa44375f5..2197c4857 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -699,12 +699,12 @@ function* numbers () { } yield 6; } -var g = numbers() -g.next() // { done: false, value: 1 } -g.next() // { done: false, value: 2 } -g.return(7) // { done: false, value: 4 } -g.next() // { done: false, value: 5 } -g.next() // { done: true, value: 7 } +var g = numbers(); +g.next() // { value: 1, done: false } +g.next() // { value: 2, done: false } +g.return(7) // { value: 4, done: false } +g.next() // { value: 5, done: false } +g.next() // { value: 7, done: true } ``` 上面代码中,调用`return`方法后,就开始执行`finally`代码块,然后等到`finally`代码块执行完,再执行`return`方法。 From 5a7e690e5f148333987c8a249df67e538b20f623 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 12 Apr 2017 13:14:13 +0800 Subject: [PATCH 017/768] =?UTF-8?q?=E4=BF=AE=E6=AD=A3querySelectorAll?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=9A=84NodeList=E9=9B=86=E5=90=88=E4=B8=8D?= =?UTF-8?q?=E8=83=BD=E4=BD=BF=E7=94=A8forEach=E6=96=B9=E6=B3=95=E9=81=8D?= =?UTF-8?q?=E5=8E=86=E7=9A=84=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正querySelectorAll返回的NodeList集合不能使用forEach方法遍历的描述. 1.在chrome中querySelectorAll返回的NodeList集合可以使用forEach方法遍历 2.document.querySelectorAll('td').forEach(function (p) { console.log(p instanceof Object); }); 输出: 6 true undefined --- docs/array.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/array.md b/docs/array.md index f3626270b..49097b510 100644 --- a/docs/array.md +++ b/docs/array.md @@ -37,7 +37,7 @@ function foo() { } ``` -上面代码中,`querySelectorAll`方法返回的是一个类似数组的对象,只有将这个对象转为真正的数组,才能使用`forEach`方法。 +上面代码中,`querySelectorAll`方法返回的是一个类似数组的对象,可以将这个对象转为真正的数组,再使用`forEach`方法。 只要是部署了Iterator接口的数据结构,`Array.from`都能将其转为数组。 From 3aed4ae0fbee54534bd15c4174f2d22a4fb27bb6 Mon Sep 17 00:00:00 2001 From: Jacty Date: Thu, 13 Apr 2017 02:58:57 +0800 Subject: [PATCH 018/768] typo mistake typo mistake --- docs/string.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/string.md b/docs/string.md index 735c5a1b8..0bb4c7f57 100644 --- a/docs/string.md +++ b/docs/string.md @@ -124,7 +124,7 @@ String.fromCharCode(0x20BB7) 上面代码中,`String.fromCharCode`不能识别大于`0xFFFF`的码点,所以`0x20BB7`就发生了溢出,最高位`2`被舍弃了,最后返回码点`U+0BB7`对应的字符,而不是码点`U+20BB7`对应的字符。 -ES6提供了`String.fromCodePoint`方法,可以识别`0xFFFF`的字符,弥补了`String.fromCharCode`方法的不足。在作用上,正好与`codePointAt`方法相反。 +ES6提供了`String.fromCodePoint`方法,可以识别大于`0xFFFF`的字符,弥补了`String.fromCharCode`方法的不足。在作用上,正好与`codePointAt`方法相反。 ```javascript String.fromCodePoint(0x20BB7) From c17b484d0e56c33549bbf92399494300407ffc86 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 13 Apr 2017 17:23:37 +0800 Subject: [PATCH 019/768] docs(set): edit map --- docs/set-map.md | 394 +++++++++++++++++++++++++++++++----------------- 1 file changed, 252 insertions(+), 142 deletions(-) diff --git a/docs/set-map.md b/docs/set-map.md index ebba6ded6..7486a3307 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -25,12 +25,12 @@ Set 函数可以接受一个数组(或类似数组的对象)作为参数, ```javascript // 例一 -var set = new Set([1, 2, 3, 4, 4]); +const set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4] // 例二 -var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); +const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); items.size // 5 // 例三 @@ -38,7 +38,7 @@ function divs () { return [...document.querySelectorAll('div')]; } -var set = new Set(divs()); +const set = new Set(divs()); set.size // 56 // 类似于 @@ -116,7 +116,7 @@ s.has(2) // false ```javascript // 对象的写法 -var properties = { +const properties = { 'width': 1, 'height': 1 }; @@ -126,7 +126,7 @@ if (properties[someName]) { } // Set的写法 -var properties = new Set(); +const properties = new Set(); properties.add('width'); properties.add('height'); @@ -136,11 +136,11 @@ if (properties.has(someName)) { } ``` -`Array.from`方法可以将Set结构转为数组。 +`Array.from`方法可以将 Set 结构转为数组。 ```javascript -var items = new Set([1, 2, 3, 4, 5]); -var array = Array.from(items); +const items = new Set([1, 2, 3, 4, 5]); +const array = Array.from(items); ``` 这就提供了去除数组重复成员的另一种方法。 @@ -296,59 +296,60 @@ set = new Set(Array.from(set, val => val * 2)); ## WeakSet -WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别。 +WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。 -首先,WeakSet的成员只能是对象,而不能是其他类型的值。 - -其次,WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。 +首先,WeakSet 的成员只能是对象,而不能是其他类型的值。 ```javascript -var ws = new WeakSet(); +const ws = new WeakSet(); ws.add(1) // TypeError: Invalid value used in weak set ws.add(Symbol()) // TypeError: invalid value used in weak set ``` -上面代码试图向WeakSet添加一个数值和`Symbol`值,结果报错,因为WeakSet只能放置对象。 +上面代码试图向 WeakSet 添加一个数值和`Symbol`值,结果报错,因为 WeakSet 只能放置对象。 + +其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。这个特点意味着,无法引用 WeakSet 的成员,因此 WeakSet 是不可遍历的。 -WeakSet是一个构造函数,可以使用`new`命令,创建WeakSet数据结构。 +WeakSet 是一个构造函数,可以使用`new`命令,创建 WeakSet 数据结构。 ```javascript -var ws = new WeakSet(); +const ws = new WeakSet(); ``` -作为构造函数,WeakSet可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有iterable接口的对象,都可以作为WeakSet的参数。)该数组的所有成员,都会自动成为WeakSet实例对象的成员。 +作为构造函数,WeakSet 可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数。)该数组的所有成员,都会自动成为 WeakSet 实例对象的成员。 ```javascript -var a = [[1,2], [3,4]]; -var ws = new WeakSet(a); +const a = [[1, 2], [3, 4]]; +const ws = new WeakSet(a); +// WeakSet {[1, 2], [3, 4]} ``` -上面代码中,`a`是一个数组,它有两个成员,也都是数组。将`a`作为WeakSet构造函数的参数,`a`的成员会自动成为WeakSet的成员。 +上面代码中,`a`是一个数组,它有两个成员,也都是数组。将`a`作为 WeakSet 构造函数的参数,`a`的成员会自动成为 WeakSet 的成员。 -注意,是`a`数组的成员成为WeakSet的成员,而不是`a`数组本身。这意味着,数组的成员只能是对象。 +注意,是`a`数组的成员成为 WeakSet 的成员,而不是`a`数组本身。这意味着,数组的成员只能是对象。 ```javascript -var b = [3, 4]; -var ws = new WeakSet(b); +const b = [3, 4]; +const ws = new WeakSet(b); // Uncaught TypeError: Invalid value used in weak set(…) ``` -上面代码中,数组`b`的成员不是对象,加入WeaKSet就会报错。 +上面代码中,数组`b`的成员不是对象,加入 WeaKSet 就会报错。 -WeakSet结构有以下三个方法。 +WeakSet 结构有以下三个方法。 -- **WeakSet.prototype.add(value)**:向WeakSet实例添加一个新成员。 -- **WeakSet.prototype.delete(value)**:清除WeakSet实例的指定成员。 -- **WeakSet.prototype.has(value)**:返回一个布尔值,表示某个值是否在WeakSet实例之中。 +- **WeakSet.prototype.add(value)**:向 WeakSet 实例添加一个新成员。 +- **WeakSet.prototype.delete(value)**:清除 WeakSet 实例的指定成员。 +- **WeakSet.prototype.has(value)**:返回一个布尔值,表示某个值是否在 WeakSet 实例之中。 下面是一个例子。 ```javascript -var ws = new WeakSet(); -var obj = {}; -var foo = {}; +const ws = new WeakSet(); +const obj = {}; +const foo = {}; ws.add(window); ws.add(obj); @@ -394,25 +395,25 @@ class Foo { ## Map -### Map结构的目的和基本用法 +### 含义和基本用法 -JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。 +JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。 ```javascript -var data = {}; -var element = document.getElementById('myDiv'); +const data = {}; +const element = document.getElementById('myDiv'); data[element] = 'metadata'; data['[object HTMLDivElement]'] // "metadata" ``` -上面代码原意是将一个DOM节点作为对象`data`的键,但是由于对象只接受字符串作为键名,所以`element`被自动转为字符串`[object HTMLDivElement]`。 +上面代码原意是将一个 DOM 节点作为对象`data`的键,但是由于对象只接受字符串作为键名,所以`element`被自动转为字符串`[object HTMLDivElement]`。 -为了解决这个问题,ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。 +为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。 ```javascript -var m = new Map(); -var o = {p: 'Hello World'}; +const m = new Map(); +const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" @@ -422,12 +423,12 @@ m.delete(o) // true m.has(o) // false ``` -上面代码使用`set`方法,将对象`o`当作`m`的一个键,然后又使用`get`方法读取这个键,接着使用`delete`方法删除了这个键。 +上面代码使用 Map 结构的`set`方法,将对象`o`当作`m`的一个键,然后又使用`get`方法读取这个键,接着使用`delete`方法删除了这个键。 -作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。 +上面的例子展示了如何向 Map 添加成员。作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。 ```javascript -var map = new Map([ +const map = new Map([ ['name', '张三'], ['title', 'Author'] ]); @@ -439,35 +440,44 @@ map.has('title') // true map.get('title') // "Author" ``` -上面代码在新建Map实例时,就指定了两个键`name`和`title`。 +上面代码在新建 Map 实例时,就指定了两个键`name`和`title`。 -Map构造函数接受数组作为参数,实际上执行的是下面的算法。 +`Map`构造函数接受数组作为参数,实际上执行的是下面的算法。 ```javascript -var items = [ +const items = [ ['name', '张三'], ['title', 'Author'] ]; -var map = new Map(); -items.forEach(([key, value]) => map.set(key, value)); + +const map = new Map(); + +items.forEach( + ([key, value]) => map.set(key, value) +); ``` -下面的例子中,字符串`true`和布尔值`true`是两个不同的键。 +事实上,不仅仅是数组,任何具有 Iterator 接口的数据结构(详见《Iterator》一章)都可以当作`Map`构造函数的参数。这就是说,`Set`和`Map`都可以用来生成新的 Map。 ```javascript -var m = new Map([ - [true, 'foo'], - ['true', 'bar'] +const set = new Set([ + ['foo', 1], + ['bar', 2] ]); +const m1 = new Map(set); +m1.get('foo') // 1 -m.get(true) // 'foo' -m.get('true') // 'bar' +const m2 = new Map([['baz', 3]]); +const m3 = new Map(m2); +m3.get('baz') // 3 ``` +上面代码中,我们分别使用 Set 对象和 Map 对象,当作`Map`构造函数的参数,结果都生成了新的 Map 对象。 + 如果对同一个键多次赋值,后面的值将覆盖前面的值。 ```javascript -let map = new Map(); +const map = new Map(); map .set(1, 'aaa') @@ -485,10 +495,10 @@ new Map().get('asfddfsasadf') // undefined ``` -注意,只有对同一个对象的引用,Map结构才将其视为同一个键。这一点要非常小心。 +注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。 ```javascript -var map = new Map(); +const map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined @@ -496,13 +506,13 @@ map.get(['a']) // undefined 上面代码的`set`和`get`方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此`get`方法无法读取该键,返回`undefined`。 -同理,同样的值的两个实例,在Map结构中被视为两个键。 +同理,同样的值的两个实例,在 Map 结构中被视为两个键。 ```javascript -var map = new Map(); +const map = new Map(); -var k1 = ['a']; -var k2 = ['a']; +const k1 = ['a']; +const k2 = ['a']; map .set(k1, 111) @@ -512,20 +522,28 @@ map.get(k1) // 111 map.get(k2) // 222 ``` -上面代码中,变量`k1`和`k2`的值是一样的,但是它们在Map结构中被视为两个键。 +上面代码中,变量`k1`和`k2`的值是一样的,但是它们在 Map 结构中被视为两个键。 -由上可知,Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。 +由上可知,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。 -如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map将其视为一个键,包括`0`和`-0`。另外,虽然`NaN`不严格相等于自身,但Map将其视为同一个键。 +如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,包括`0`和`-0`,布尔值`true`和字符串`true`则是两个不同的键。另外,`undefined`和`null`也是两个不同的键。虽然`NaN`不严格相等于自身,但 Map 将其视为同一个键。 ```javascript let map = new Map(); -map.set(NaN, 123); -map.get(NaN) // 123 - map.set(-0, 123); map.get(+0) // 123 + +map.set(true, 1); +map.set('true', 2); +map.get(true) // 1 + +map.set(undefined, 3); +map.set(null, 4); +map.get(undefined) // 3 + +map.set(NaN, 123); +map.get(NaN) // 123 ``` ### 实例的属性和操作方法 @@ -534,10 +552,10 @@ Map结构的实例有以下属性和操作方法。 **(1)size属性** -`size`属性返回Map结构的成员总数。 +`size`属性返回 Map 结构的成员总数。 ```javascript -let map = new Map(); +const map = new Map(); map.set('foo', true); map.set('bar', false); @@ -546,17 +564,17 @@ map.size // 2 **(2)set(key, value)** -`set`方法设置`key`所对应的键值,然后返回整个Map结构。如果`key`已经有值,则键值会被更新,否则就新生成该键。 +`set`方法设置键名`key`对应的键值为`value`,然后返回整个 Map 结构。如果`key`已经有值,则键值会被更新,否则就新生成该键。 ```javascript -var m = new Map(); +const m = new Map(); -m.set("edition", 6) // 键是字符串 -m.set(262, "standard") // 键是数值 -m.set(undefined, "nah") // 键是undefined +m.set('edition', 6) // 键是字符串 +m.set(262, 'standard') // 键是数值 +m.set(undefined, 'nah') // 键是 undefined ``` -`set`方法返回的是Map本身,因此可以采用链式写法。 +`set`方法返回的是当前的`Map`对象,因此可以采用链式写法。 ```javascript let map = new Map() @@ -570,43 +588,44 @@ let map = new Map() `get`方法读取`key`对应的键值,如果找不到`key`,返回`undefined`。 ```javascript -var m = new Map(); +const m = new Map(); -var hello = function() {console.log("hello");} -m.set(hello, "Hello ES6!") // 键是函数 +const hello = function() {console.log('hello');}; +m.set(hello, 'Hello ES6!') // 键是函数 m.get(hello) // Hello ES6! ``` **(4)has(key)** -`has`方法返回一个布尔值,表示某个键是否在Map数据结构中。 +`has`方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。 ```javascript -var m = new Map(); +const m = new Map(); -m.set("edition", 6); -m.set(262, "standard"); -m.set(undefined, "nah"); +m.set('edition', 6); +m.set(262, 'standard'); +m.set(undefined, 'nah'); -m.has("edition") // true -m.has("years") // false +m.has('edition') // true +m.has('years') // false m.has(262) // true m.has(undefined) // true ``` **(5)delete(key)** -`delete`方法删除某个键,返回true。如果删除失败,返回false。 +`delete`方法删除某个键,返回`true`。如果删除失败,返回`false`。 ```javascript -var m = new Map(); -m.set(undefined, "nah"); +const m = new Map(); +m.set(undefined, 'nah'); m.has(undefined) // true m.delete(undefined) m.has(undefined) // false ``` + **(6)clear()** `clear`方法清除所有成员,没有返回值。 @@ -623,19 +642,17 @@ map.size // 0 ### 遍历方法 -Map原生提供三个遍历器生成函数和一个遍历方法。 +Map 结构原生提供三个遍历器生成函数和一个遍历方法。 - `keys()`:返回键名的遍历器。 - `values()`:返回键值的遍历器。 - `entries()`:返回所有成员的遍历器。 -- `forEach()`:遍历Map的所有成员。 +- `forEach()`:遍历 Map 的所有成员。 -需要特别注意的是,Map的遍历顺序就是插入顺序。 - -下面是使用实例。 +需要特别注意的是,Map 的遍历顺序就是插入顺序。 ```javascript -let map = new Map([ +const map = new Map([ ['F', 'no'], ['T', 'yes'], ]); @@ -662,24 +679,28 @@ for (let item of map.entries()) { for (let [key, value] of map.entries()) { console.log(key, value); } +// "F" "no" +// "T" "yes" // 等同于使用map.entries() for (let [key, value] of map) { console.log(key, value); } +// "F" "no" +// "T" "yes" ``` -上面代码最后的那个例子,表示Map结构的默认遍历器接口(`Symbol.iterator`属性),就是`entries`方法。 +上面代码最后的那个例子,表示 Map 结构的默认遍历器接口(`Symbol.iterator`属性),就是`entries`方法。 ```javascript map[Symbol.iterator] === map.entries // true ``` -Map结构转为数组结构,比较快速的方法是结合使用扩展运算符(`...`)。 +Map 结构转为数组结构,比较快速的方法是使用扩展运算符(`...`)。 ```javascript -let map = new Map([ +const map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], @@ -698,26 +719,26 @@ let map = new Map([ // [[1,'one'], [2, 'two'], [3, 'three']] ``` -结合数组的`map`方法、`filter`方法,可以实现Map的遍历和过滤(Map本身没有`map`和`filter`方法)。 +结合数组的`map`方法、`filter`方法,可以实现 Map 的遍历和过滤(Map 本身没有`map`和`filter`方法)。 ```javascript -let map0 = new Map() +const map0 = new Map() .set(1, 'a') .set(2, 'b') .set(3, 'c'); -let map1 = new Map( +const map1 = new Map( [...map0].filter(([k, v]) => k < 3) ); -// 产生Map结构 {1 => 'a', 2 => 'b'} +// 产生 Map 结构 {1 => 'a', 2 => 'b'} -let map2 = new Map( +const map2 = new Map( [...map0].map(([k, v]) => [k * 2, '_' + v]) ); -// 产生Map结构 {2 => '_a', 4 => '_b', 6 => '_c'} +// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'} ``` -此外,Map还有一个`forEach`方法,与数组的`forEach`方法类似,也可以实现遍历。 +此外,Map 还有一个`forEach`方法,与数组的`forEach`方法类似,也可以实现遍历。 ```javascript map.forEach(function(value, key, map) { @@ -728,7 +749,7 @@ map.forEach(function(value, key, map) { `forEach`方法还可以接受第二个参数,用来绑定`this`。 ```javascript -var reporter = { +const reporter = { report: function(key, value) { console.log("Key: %s, Value: %s", key, value); } @@ -743,28 +764,36 @@ map.forEach(function(value, key, map) { ### 与其他数据结构的互相转换 -**(1)Map转为数组** +**(1)Map 转为数组** -前面已经提过,Map转为数组最方便的方法,就是使用扩展运算符(...)。 +前面已经提过,Map 转为数组最方便的方法,就是使用扩展运算符(`...`)。 ```javascript -let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']); +const myMap = new Map() + .set(true, 7) + .set({foo: 3}, ['abc']); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ] ``` -**(2)数组转为Map** +**(2)数组 转为 Map** -将数组转入Map构造函数,就可以转为Map。 +将数组转入 Map 构造函数,就可以转为 Map。 ```javascript -new Map([[true, 7], [{foo: 3}, ['abc']]]) -// Map {true => 7, Object {foo: 3} => ['abc']} +new Map([ + [true, 7], + [{foo: 3}, ['abc']] +]) +// Map { +// true => 7, +// Object {foo: 3} => ['abc'] +// } ``` -**(3)Map转为对象** +**(3)Map 转为对象** -如果所有Map的键都是字符串,它可以转为对象。 +如果所有 Map 的键都是字符串,它可以转为对象。 ```javascript function strMapToObj(strMap) { @@ -775,12 +804,14 @@ function strMapToObj(strMap) { return obj; } -let myMap = new Map().set('yes', true).set('no', false); +const myMap = new Map() + .set('yes', true) + .set('no', false); strMapToObj(myMap) // { yes: true, no: false } ``` -**(4)对象转为Map** +**(4)对象转为 Map** ```javascript function objToStrMap(obj) { @@ -792,12 +823,12 @@ function objToStrMap(obj) { } objToStrMap({yes: true, no: false}) -// [ [ 'yes', true ], [ 'no', false ] ] +// Map {"yes" => true, "no" => false} ``` -**(5)Map转为JSON** +**(5)Map 转为 JSON** -Map转为JSON要区分两种情况。一种情况是,Map的键名都是字符串,这时可以选择转为对象JSON。 +Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。 ```javascript function strMapToJson(strMap) { @@ -809,7 +840,7 @@ strMapToJson(myMap) // '{"yes":true,"no":false}' ``` -另一种情况是,Map的键名有非字符串,这时可以选择转为数组JSON。 +另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。 ```javascript function mapToArrayJson(map) { @@ -821,20 +852,20 @@ mapToArrayJson(myMap) // '[[true,7],[{"foo":3},["abc"]]]' ``` -**(6)JSON转为Map** +**(6)JSON 转为 Map** -JSON转为Map,正常情况下,所有键名都是字符串。 +JSON 转为 Map,正常情况下,所有键名都是字符串。 ```javascript function jsonToStrMap(jsonStr) { return objToStrMap(JSON.parse(jsonStr)); } -jsonToStrMap('{"yes":true,"no":false}') +jsonToStrMap('{"yes": true, "no": false}') // Map {'yes' => true, 'no' => false} ``` -但是,有一种特殊情况,整个JSON就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为Map。这往往是数组转为JSON的逆操作。 +但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为Map。这往往是数组转为 JSON 的逆操作。 ```javascript function jsonToMap(jsonStr) { @@ -847,27 +878,74 @@ jsonToMap('[[true,7],[{"foo":3},["abc"]]]') ## WeakMap -`WeakMap`结构与`Map`结构基本类似,唯一的区别是它只接受对象作为键名(`null`除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。 +### 含义和用法 + +`WeakMap`结构与`Map`结构类似,也是用于生成键值对。 + +```javascript +// WeakMap 可以使用 set 方法添加成员 +const wm1 = new WeakMap(); +const key = {foo: 1}; +wm1.set(key, 2); +wm1.get(key) // 2 + +// WeakMap 也可以接受一个数组, +// 作为构造函数的参数 +const k1 = [1, 2, 3]; +const k2 = [4, 5, 6]; +const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]); +wm2.get(k2) // "bar" +``` + +`WeakMap`与`Map`的区别有两点。 + +首先,`WeakMap`只接受对象作为键名(`null`除外),不接受其他类型的值作为键名。 ```javascript -var map = new WeakMap() +const map = new WeakMap(); map.set(1, 2) // TypeError: 1 is not an object! map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key ``` -上面代码中,如果将`1`和`Symbol`作为WeakMap的键名,都会报错。 +上面代码中,如果将数值`1`和`Symbol`值作为 WeakMap 的键名,都会报错。 -`WeakMap`的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,`WeakMap`自动移除对应的键值对。典型应用是,一个对应DOM元素的`WeakMap`结构,当某个DOM元素被清除,其所对应的`WeakMap`记录就会自动被移除。基本上,`WeakMap`的专用场合就是,它的键所对应的对象,可能会在将来消失。`WeakMap`结构有助于防止内存泄漏。 +其次,`WeakMap`的键名所指向的对象,不计入垃圾回收机制。 -下面是`WeakMap`结构的一个例子,可以看到用法上与`Map`几乎一样。 +`WeakMap`的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。请看下面的例子。 ```javascript -var wm = new WeakMap(); -var element = document.querySelector(".element"); +const e1 = document.getElementById('foo'); +const e2 = document.getElementById('bar'); +const arr = [ + [e1, 'foo 元素'], + [e2, 'bar 元素'], +]; +``` -wm.set(element, "Original"); +上面代码中,`e1`和`e2`是两个对象,我们通过`arr`数组对这两个对象添加一些文字说明。这就形成了`arr`对`e1`和`e2`的引用。 + +一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放`e1`和`e2`占用的内存。 + +```javascript +// 不需要 e1 和 e2 的时候 +// 必须手动删除引用 +arr [0] = null; +arr [1] = null; +``` + +上面这样的写法显然很不方便。一旦忘了写,就会造成内存泄露。 + +WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。 + +基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用`WeakMap`结构。当该 DOM 元素被清除,其所对应的`WeakMap`记录就会自动被移除。 + +```javascript +const wm = new WeakMap(); +const element = document.querySelector('.element'); + +wm.set(element, 'Original'); wm.get(element) // "Original" element.parentNode.removeChild(element); @@ -877,19 +955,37 @@ wm.get(element) // undefined 上面代码中,变量`wm`是一个`WeakMap`实例,我们将一个`DOM`节点`element`作为键名,然后销毁这个节点,`element`对应的键就自动消失了,再引用这个键名就返回`undefined`。 -WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有`key()`、`values()`和`entries()`方法),也没有`size`属性;二是无法清空,即不支持`clear`方法。这与`WeakMap`的键不被计入引用、被垃圾回收机制忽略有关。因此,`WeakMap`只有四个方法可用:`get()`、`set()`、`has()`、`delete()`。 +总之,`WeakMap`的专用场合就是,它的键所对应的对象,可能会在将来消失。`WeakMap`结构有助于防止内存泄漏。 + +注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。 ```javascript -var wm = new WeakMap(); +const wm = new WeakMap(); +let key = {}; +let obj = {foo: 1}; + +wm.set(key, obj); +obj = null; +wm.get(key) +// Object {foo: 1} +``` -wm.size -// undefined +上面代码中,键值`obj`是正常引用。所以,即使在 WeakMap 外部消除了`obj`的引用,WeakMap 内部的引用依然存在。 -wm.forEach -// undefined +WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操作(即没有`key()`、`values()`和`entries()`方法),也没有`size`属性。因为没有办法列出所有键名,这个键名是否存在完全不可预测,跟垃圾回收机制是否运行相关。二是无法清空,即不支持`clear`方法。因此,`WeakMap`只有四个方法可用:`get()`、`set()`、`has()`、`delete()`。 + +```javascript +const wm = new WeakMap(); + +// size、forEach、clear 方法都不存在 +wm.size // undefined +wm.forEach // undefined +wm.clear // undefined ``` -前文说过,WeakMap应用的典型场合就是DOM节点作为键名。下面是一个例子。 +### WeakMap 的用途 + +前文说过,WeakMap 应用的典型场合就是 DOM 节点作为键名。下面是一个例子。 ```javascript let myElement = document.getElementById('logo'); @@ -905,11 +1001,25 @@ myElement.addEventListener('click', function() { 上面代码中,`myElement`是一个 DOM 节点,每当发生`click`事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是`myElement`。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。 +进一步说,注册监听事件的`listener`对象,就很合适用 WeakMap 实现。 + +```javascript +const listener = new WeakMap(); + +listener.set(element1, handler1); +listener.set(element2, handler2); + +element1.addEventListener('click', listener.get(element1), false); +element2.addEventListener('click', listener.get(element2), false); +``` + +上面代码中,监听函数放在 WeakMap 里面。一旦 DOM 对象消失,跟它绑定的监听函数也会自动消失。 + WeakMap 的另一个用处是部署私有属性。 ```javascript -let _counter = new WeakMap(); -let _action = new WeakMap(); +const _counter = new WeakMap(); +const _action = new WeakMap(); class Countdown { constructor(counter, action) { @@ -927,11 +1037,11 @@ class Countdown { } } -let c = new Countdown(2, () => console.log('DONE')); +const c = new Countdown(2, () => console.log('DONE')); c.dec() c.dec() // DONE ``` -上面代码中,Countdown类的两个内部属性`_counter`和`_action`,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。 +上面代码中,`Countdown`类的两个内部属性`_counter`和`_action`,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。 From b330d721acc81af02b67dd62ffc6108b3ffdcf09 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 13 Apr 2017 21:36:16 +0800 Subject: [PATCH 020/768] =?UTF-8?q?bar()=E8=BE=93=E5=87=BA=E8=A1=A8?= =?UTF-8?q?=E8=BF=B0=E6=9C=89=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bar() 输出表述有误, outer 应该在 bar() 运行时输出! --- docs/function.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/function.md b/docs/function.md index 3f3c61acf..65273fcd0 100644 --- a/docs/function.md +++ b/docs/function.md @@ -301,10 +301,10 @@ let foo = 'outer'; function bar(func = x => foo) { let foo = 'inner'; - console.log(func()); // outer + console.log(func()); } -bar(); +bar(); // outer ``` 上面代码中,函数`bar`的参数`func`的默认值是一个匿名函数,返回值为变量`foo`。函数参数形成的单独作用域里面,并没有定义变量`foo`,所以`foo`指向外层的全局变量`foo`,因此输出`outer`。 From c582198e7a7dbf0446622464dfe5541dcdd2995d Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 13 Apr 2017 22:09:46 +0800 Subject: [PATCH 021/768] =?UTF-8?q?=E8=AF=AD=E4=B9=89=E4=B8=8D=E6=B8=85?= =?UTF-8?q?=E6=99=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 语义不清晰。 2. 引入 Python 作为比较,对于不了解 Python 的读者并无意义。 --- docs/function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/function.md b/docs/function.md index 3f3c61acf..a8c807347 100644 --- a/docs/function.md +++ b/docs/function.md @@ -371,7 +371,7 @@ foo() 上面代码的`foo`函数,如果调用的时候没有参数,就会调用默认值`throwIfMissing`函数,从而抛出一个错误。 -从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与 Python 语言不一样。 +从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(注意函数名throwIfMissing之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。 另外,可以将参数默认值设为`undefined`,表明这个参数是可以省略的。 From ca2a2fc8b3a45a1706bb0b974f0554a3a4e78f32 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 14 Apr 2017 13:25:58 +0800 Subject: [PATCH 022/768] docs(function): edit function --- docs/function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/function.md b/docs/function.md index 1a80f5093..1d44bdc44 100644 --- a/docs/function.md +++ b/docs/function.md @@ -371,7 +371,7 @@ foo() 上面代码的`foo`函数,如果调用的时候没有参数,就会调用默认值`throwIfMissing`函数,从而抛出一个错误。 -从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(注意函数名throwIfMissing之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。 +从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(注意函数名`throwIfMissing`之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。 另外,可以将参数默认值设为`undefined`,表明这个参数是可以省略的。 From e1bfba24ed128b33046277244aa5535d9bee8d24 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 17 Apr 2017 12:56:24 +0800 Subject: [PATCH 023/768] docs(set): edit weakMap & weakSet --- docs/set-map.md | 144 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 26 deletions(-) diff --git a/docs/set-map.md b/docs/set-map.md index 7486a3307..d07c284b8 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -66,7 +66,7 @@ set.add(b); set // Set {NaN} ``` -上面代码向Set实例添加了两个`NaN`,但是只能加入一个。这表明,在Set内部,两个`NaN`是相等。 +上面代码向 Set 实例添加了两个`NaN`,但是只能加入一个。这表明,在 Set 内部,两个`NaN`是相等。 另外,两个对象总是不相等的。 @@ -82,14 +82,14 @@ set.size // 2 上面代码表示,由于两个空对象不相等,所以它们被视为两个值。 -### Set实例的属性和方法 +### Set 实例的属性和方法 -Set结构的实例有以下属性。 +Set 结构的实例有以下属性。 - `Set.prototype.constructor`:构造函数,默认就是`Set`函数。 - `Set.prototype.size`:返回`Set`实例的成员总数。 -Set实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。 +Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。 - `add(value)`:添加某个值,返回Set结构本身。 - `delete(value)`:删除某个值,返回一个布尔值,表示删除是否成功。 @@ -155,7 +155,7 @@ dedupe([1, 1, 2, 3]) // [1, 2, 3] ### 遍历操作 -Set结构的实例有四个遍历方法,可以用于遍历成员。 +Set 结构的实例有四个遍历方法,可以用于遍历成员。 - `keys()`:返回键名的遍历器 - `values()`:返回键值的遍历器 @@ -195,14 +195,14 @@ for (let item of set.entries()) { 上面代码中,`entries`方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。 -Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的`values`方法。 +Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的`values`方法。 ```javascript Set.prototype[Symbol.iterator] === Set.prototype.values // true ``` -这意味着,可以省略`values`方法,直接用`for...of`循环遍历Set。 +这意味着,可以省略`values`方法,直接用`for...of`循环遍历 Set。 ```javascript let set = new Set(['red', 'green', 'blue']); @@ -227,11 +227,11 @@ set.forEach((value, key) => console.log(value * 2) ) // 6 ``` -上面代码说明,`forEach`方法的参数就是一个处理函数。该函数的参数依次为键值、键名、集合本身(上例省略了该参数)。另外,`forEach`方法还可以有第二个参数,表示绑定的this对象。 +上面代码说明,`forEach`方法的参数就是一个处理函数。该函数的参数依次为键值、键名、集合本身(上例省略了该参数)。另外,`forEach`方法还可以有第二个参数,表示绑定的`this`对象。 **(3)遍历的应用** -扩展运算符(`...`)内部使用`for...of`循环,所以也可以用于Set结构。 +扩展运算符(`...`)内部使用`for...of`循环,所以也可以用于 Set 结构。 ```javascript let set = new Set(['red', 'green', 'blue']); @@ -239,7 +239,7 @@ let arr = [...set]; // ['red', 'green', 'blue'] ``` -扩展运算符和Set结构相结合,就可以去除数组的重复成员。 +扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。 ```javascript let arr = [3, 5, 2, 2, 5, 5]; @@ -247,7 +247,7 @@ let unique = [...new Set(arr)]; // [3, 5, 2] ``` -而且,数组的`map`和`filter`方法也可以用于Set了。 +而且,数组的`map`和`filter`方法也可以用于 Set 了。 ```javascript let set = new Set([1, 2, 3]); @@ -259,7 +259,7 @@ set = new Set([...set].filter(x => (x % 2) == 0)); // 返回Set结构:{2, 4} ``` -因此使用Set可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。 +因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。 ```javascript let a = new Set([1, 2, 3]); @@ -278,7 +278,7 @@ let difference = new Set([...a].filter(x => !b.has(x))); // Set {1} ``` -如果想在遍历操作中,同步改变原来的Set结构,目前没有直接的方法,但有两种变通方法。一种是利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构;另一种是利用`Array.from`方法。 +如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用`Array.from`方法。 ```javascript // 方法一 @@ -292,10 +292,12 @@ set = new Set(Array.from(set, val => val * 2)); // set的值是2, 4, 6 ``` -上面代码提供了两种方法,直接在遍历操作中改变原来的Set结构。 +上面代码提供了两种方法,直接在遍历操作中改变原来的 Set 结构。 ## WeakSet +### 含义 + WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。 首先,WeakSet 的成员只能是对象,而不能是其他类型的值。 @@ -310,7 +312,15 @@ ws.add(Symbol()) 上面代码试图向 WeakSet 添加一个数值和`Symbol`值,结果报错,因为 WeakSet 只能放置对象。 -其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。这个特点意味着,无法引用 WeakSet 的成员,因此 WeakSet 是不可遍历的。 +其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。 + +这是因为垃圾回收机制依赖引用计数,如果一个值的引用次数不为`0`,垃圾回收机制就不会释放这块内存。对于那些不重要的引用,在结束使用之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakMap 里面的引用就会自动消失。 + +由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。 + +这些特点同样适用于本章后面要介绍的 WeakMap 结构。 + +### 语法 WeakSet 是一个构造函数,可以使用`new`命令,创建 WeakSet 数据结构。 @@ -373,9 +383,9 @@ ws.forEach(function(item){ console.log('WeakSet has ' + item)}) 上面代码试图获取`size`和`forEach`属性,结果都不能成功。 -WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。 +WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。 -下面是WeakSet的另一个例子。 +下面是 WeakSet 的另一个例子。 ```javascript const foos = new WeakSet() @@ -548,7 +558,7 @@ map.get(NaN) // 123 ### 实例的属性和操作方法 -Map结构的实例有以下属性和操作方法。 +Map 结构的实例有以下属性和操作方法。 **(1)size属性** @@ -878,7 +888,7 @@ jsonToMap('[[true,7],[{"foo":3},["abc"]]]') ## WeakMap -### 含义和用法 +### 含义 `WeakMap`结构与`Map`结构类似,也是用于生成键值对。 @@ -907,6 +917,8 @@ map.set(1, 2) // TypeError: 1 is not an object! map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key +map.set(null, 2) +// TypeError: Invalid value used as weak map key ``` 上面代码中,如果将数值`1`和`Symbol`值作为 WeakMap 的键名,都会报错。 @@ -943,17 +955,16 @@ WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对 ```javascript const wm = new WeakMap(); -const element = document.querySelector('.element'); -wm.set(element, 'Original'); -wm.get(element) // "Original" +const element = document.getElementById('example'); -element.parentNode.removeChild(element); -element = null; -wm.get(element) // undefined +wm.set(element, 'some information'); +wm.get(element) // "some information" ``` -上面代码中,变量`wm`是一个`WeakMap`实例,我们将一个`DOM`节点`element`作为键名,然后销毁这个节点,`element`对应的键就自动消失了,再引用这个键名就返回`undefined`。 +上面代码中,先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对`element`的引用就是弱引用,不会被计入垃圾回收机制。 + +也就是说,上面的 DOM 节点对象的引用计数是`1`,而不是`2`。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。 总之,`WeakMap`的专用场合就是,它的键所对应的对象,可能会在将来消失。`WeakMap`结构有助于防止内存泄漏。 @@ -972,6 +983,87 @@ wm.get(key) 上面代码中,键值`obj`是正常引用。所以,即使在 WeakMap 外部消除了`obj`的引用,WeakMap 内部的引用依然存在。 +### WeakMap 的示例 + +WeakMap 的例子很难演示,因为无法观察它里面的引用会自动消失。此时,其他引用都解除了,已经没有引用指向 WeakMap 的键名了,导致无法证实那个键名是不是存在。 + +贺师俊老师[提示](https://github.com/ruanyf/es6tutorial/issues/362#issuecomment-292109104),如果引用所指向的值占用特别多的内存,就可以通过 Node 的`process.memoryUsage`方法看出来。根据这个思路,网友[vtxf](https://github.com/ruanyf/es6tutorial/issues/362#issuecomment-292451925)补充了下面的例子。 + +首先,打开 Node 命令行。 + +```bash +$ node --expose-gc +``` + +上面代码中,`--expose-gc`参数表示允许手动执行垃圾回收机制。 + +然后,执行下面的代码。 + +```javascript +// 手动执行一次垃圾回收,保证获取的内存使用状态准确 +> global.gc(); +undefined + +// 查看内存占用的初始状态,heapUsed 为 4M 左右 +> process.memoryUsage(); +{ rss: 21106688, + heapTotal: 7376896, + heapUsed: 4153936, + external: 9059 } + +> let wm = new WeakMap(); +undefined + +> const b = new Object(); +undefined + +> global.gc(); +undefined + +// 此时,heapUsed 仍然为 4M 左右 +> process.memoryUsage(); +{ rss: 20537344, + heapTotal: 9474048, + heapUsed: 3967272, + external: 8993 } + +// 在 WeakMap 中添加一个键值对, +// 键名为对象 b,键值为一个 5*1024*1024 的数组 +> wm.set(b, new Array(5*1024*1024)); +WeakMap {} + +// 手动执行一次垃圾回收 +> global.gc(); +undefined + +// 此时,heapUsed 为 45M 左右 +> process.memoryUsage(); +{ rss: 62652416, + heapTotal: 51437568, + heapUsed: 45911664, + external: 8951 } + +// 解除对象 b 的引用 +> b = null; +null + +// 再次执行垃圾回收 +> global.gc(); +undefined + +// 解除 b 的引用以后,heapUsed 变回 4M 左右 +// 说明 WeakMap 中的那个长度为 5*1024*1024 的数组被销毁了 +> process.memoryUsage(); +{ rss: 20639744, + heapTotal: 8425472, + heapUsed: 3979792, + external: 8956 } +``` + +上面代码中,只要外部的引用消失,WeakMap 内部的引用,就会自动被垃圾回收清除。由此可见,有了 WeakMap 的帮助,解决内存泄漏就会简单很多。 + +### WeakMap 的语法 + WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操作(即没有`key()`、`values()`和`entries()`方法),也没有`size`属性。因为没有办法列出所有键名,这个键名是否存在完全不可预测,跟垃圾回收机制是否运行相关。二是无法清空,即不支持`clear`方法。因此,`WeakMap`只有四个方法可用:`get()`、`set()`、`has()`、`delete()`。 ```javascript From 15b962627eb36704564aaa66937449a4b37ec2b7 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 19 Apr 2017 20:16:24 +0800 Subject: [PATCH 024/768] docs(object): edit object --- docs/object.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/object.md b/docs/object.md index 62b53996c..99b091c37 100644 --- a/docs/object.md +++ b/docs/object.md @@ -1047,11 +1047,12 @@ x.a.b // 2 let o1 = { a: 1 }; let o2 = { b: 2 }; o2.__proto__ = o1; -let o3 = { ...o2 }; +let { ...o3 } = o2; o3 // { b: 2 } +o3.a // undefined ``` -上面代码中,对象`o3`是`o2`的拷贝,但是只复制了`o2`自身的属性,没有复制它的原型对象`o1`的属性。 +上面代码中,对象`o3`复制了`o2`,但是只复制了`o2`自身的属性,没有复制它的原型对象`o1`的属性。 下面是另一个例子。 From b2182920bb74a5aeaf5b37468974018ed7f36f1d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 21 Apr 2017 13:58:23 +0800 Subject: [PATCH 025/768] docs(symbol): edit symbol --- docs/function.md | 8 ++++---- docs/object.md | 4 ++-- docs/symbol.md | 46 +++++++++++++++++++++++----------------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/function.md b/docs/function.md index 1d44bdc44..dc00bbf1e 100644 --- a/docs/function.md +++ b/docs/function.md @@ -723,7 +723,7 @@ let arr = [...obj]; // TypeError: Cannot spread non-iterable object ## 严格模式 -从ES5开始,函数内部可以设定为严格模式。 +从 ES5 开始,函数内部可以设定为严格模式。 ```javascript function doSomething(a, b) { @@ -732,7 +732,7 @@ function doSomething(a, b) { } ``` -《ECMAScript 2016标准》做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。 +ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。 ```javascript // 报错 @@ -762,7 +762,7 @@ const obj = { }; ``` -这样规定的原因是,函数内部的严格模式,同时适用于函数体代码和函数参数代码。但是,函数执行的时候,先执行函数参数代码,然后再执行函数体代码。这样就有一个不合理的地方,只有从函数体代码之中,才能知道参数代码是否应该以严格模式执行,但是参数代码却应该先于函数体代码执行。 +这样规定的原因是,函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。 ```javascript // 报错 @@ -772,7 +772,7 @@ function doSomething(value = 070) { } ``` -上面代码中,参数`value`的默认值是八进制数`070`,但是严格模式下不能用前缀`0`表示八进制,所以应该报错。但是实际上,JavaScript引擎会先成功执行`value = 070`,然后进入函数体内部,发现需要用严格模式执行,这时才会报错。 +上面代码中,参数`value`的默认值是八进制数`070`,但是严格模式下不能用前缀`0`表示八进制,所以应该报错。但是实际上,JavaScript 引擎会先成功执行`value = 070`,然后进入函数体内部,发现需要用严格模式执行,这时才会报错。 虽然可以先解析函数体代码,再执行参数代码,但是这样无疑就增加了复杂性。因此,标准索性禁止了这种用法,只要参数使用了默认值、解构赋值、或者扩展运算符,就不能显式指定严格模式。 diff --git a/docs/object.md b/docs/object.md index 99b091c37..d9d33a83d 100644 --- a/docs/object.md +++ b/docs/object.md @@ -2,7 +2,7 @@ ## 属性的简洁表示法 -ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。 +ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。 ```javascript var foo = 'bar'; @@ -1066,7 +1066,7 @@ y // undefined z // 3 ``` -上面代码中,变量`x`是单纯的解构赋值,所以可以读取继承的属性;解构赋值产生的变量`y`和`z`,只能读取对象自身的属性,所以只有变量`z`可以赋值成功。 +上面代码中,变量`x`是单纯的解构赋值,所以可以读取对象`o`继承的属性;变量`y`和`z`是双重解构赋值,只能读取对象`o`自身的属性,所以只有变量`z`可以赋值成功。 解构赋值的一个用处,是扩展某个函数的参数,引入其他操作。 diff --git a/docs/symbol.md b/docs/symbol.md index 8681b4f5b..a0744c183 100644 --- a/docs/symbol.md +++ b/docs/symbol.md @@ -2,11 +2,11 @@ ## 概述 -ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。 +ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入`Symbol`的原因。 -ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。 +ES6 引入了一种新的原始数据类型`Symbol`,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:`undefined`、`null`、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。 -Symbol值通过`Symbol`函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 +Symbol 值通过`Symbol`函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 ```javascript let s = Symbol(); @@ -15,11 +15,11 @@ typeof s // "symbol" ``` -上面代码中,变量`s`就是一个独一无二的值。`typeof`运算符的结果,表明变量`s`是Symbol数据类型,而不是字符串之类的其他类型。 +上面代码中,变量`s`就是一个独一无二的值。`typeof`运算符的结果,表明变量`s`是 Symbol 数据类型,而不是字符串之类的其他类型。 -注意,`Symbol`函数前不能使用`new`命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。 +注意,`Symbol`函数前不能使用`new`命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。 -`Symbol`函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。 +`Symbol`函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。 ```javascript var s1 = Symbol('foo'); @@ -32,7 +32,7 @@ s1.toString() // "Symbol(foo)" s2.toString() // "Symbol(bar)" ``` -上面代码中,`s1`和`s2`是两个Symbol值。如果不加参数,它们在控制台的输出都是`Symbol()`,不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。 +上面代码中,`s1`和`s2`是两个 Symbol 值。如果不加参数,它们在控制台的输出都是`Symbol()`,不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。 如果 Symbol 的参数是一个对象,就会调用该对象的`toString`方法,将其转为字符串,然后才生成一个 Symbol 值。 @@ -64,7 +64,7 @@ s1 === s2 // false 上面代码中,`s1`和`s2`都是`Symbol`函数的返回值,而且参数相同,但是它们是不相等的。 -Symbol值不能与其他类型的值进行运算,会报错。 +Symbol 值不能与其他类型的值进行运算,会报错。 ```javascript var sym = Symbol('My symbol'); @@ -75,7 +75,7 @@ var sym = Symbol('My symbol'); // TypeError: can't convert symbol to string ``` -但是,Symbol值可以显式转为字符串。 +但是,Symbol 值可以显式转为字符串。 ```javascript var sym = Symbol('My symbol'); @@ -84,7 +84,7 @@ String(sym) // 'Symbol(My symbol)' sym.toString() // 'Symbol(My symbol)' ``` -另外,Symbol值也可以转为布尔值,但是不能转为数值。 +另外,Symbol 值也可以转为布尔值,但是不能转为数值。 ```javascript var sym = Symbol(); @@ -99,9 +99,9 @@ Number(sym) // TypeError sym + 2 // TypeError ``` -## 作为属性名的Symbol +## 作为属性名的 Symbol -由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。 +由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。 ```javascript var mySymbol = Symbol(); @@ -123,9 +123,9 @@ Object.defineProperty(a, mySymbol, { value: 'Hello!' }); a[mySymbol] // "Hello!" ``` -上面代码通过方括号结构和`Object.defineProperty`,将对象的属性名指定为一个Symbol值。 +上面代码通过方括号结构和`Object.defineProperty`,将对象的属性名指定为一个 Symbol 值。 -注意,Symbol值作为对象属性名时,不能用点运算符。 +注意,Symbol 值作为对象属性名时,不能用点运算符。 ```javascript var mySymbol = Symbol(); @@ -136,9 +136,9 @@ a[mySymbol] // undefined a['mySymbol'] // "Hello!" ``` -上面代码中,因为点运算符后面总是字符串,所以不会读取`mySymbol`作为标识名所指代的那个值,导致`a`的属性名实际上是一个字符串,而不是一个Symbol值。 +上面代码中,因为点运算符后面总是字符串,所以不会读取`mySymbol`作为标识名所指代的那个值,导致`a`的属性名实际上是一个字符串,而不是一个 Symbol 值。 -同理,在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。 +同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。 ```javascript let s = Symbol(); @@ -150,7 +150,7 @@ let obj = { obj[s](123); ``` -上面代码中,如果`s`不放在方括号中,该属性的键名就是字符串`s`,而不是`s`所代表的那个Symbol值。 +上面代码中,如果`s`不放在方括号中,该属性的键名就是字符串`s`,而不是`s`所代表的那个 Symbol 值。 采用增强的对象写法,上面代码的`obj`对象可以写得更简洁一些。 @@ -160,7 +160,7 @@ let obj = { }; ``` -Symbol类型还可以用于定义一组常量,保证这组常量的值都是不相等的。 +Symbol 类型还可以用于定义一组常量,保证这组常量的值都是不相等的。 ```javascript log.levels = { @@ -190,9 +190,9 @@ function getComplement(color) { } ``` -常量使用Symbol值最大的好处,就是其他任何值都不可能有相同的值了,因此可以保证上面的`switch`语句会按设计的方式工作。 +常量使用 Symbol 值最大的好处,就是其他任何值都不可能有相同的值了,因此可以保证上面的`switch`语句会按设计的方式工作。 -还有一点需要注意,Symbol值作为属性名时,该属性还是公开属性,不是私有属性。 +还有一点需要注意,Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。 ## 实例:消除魔术字符串 @@ -215,7 +215,7 @@ function getArea(shape, options) { getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串 ``` -上面代码中,字符串“Triangle”就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。 +上面代码中,字符串`Triangle`就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。 常用的消除魔术字符串的方法,就是把它写成一个变量。 @@ -237,9 +237,9 @@ function getArea(shape, options) { getArea(shapeType.triangle, { width: 100, height: 100 }); ``` -上面代码中,我们把“Triangle”写成`shapeType`对象的`triangle`属性,这样就消除了强耦合。 +上面代码中,我们把`Triangle`写成`shapeType`对象的`triangle`属性,这样就消除了强耦合。 -如果仔细分析,可以发现`shapeType.triangle`等于哪个值并不重要,只要确保不会跟其他`shapeType`属性的值冲突即可。因此,这里就很适合改用Symbol值。 +如果仔细分析,可以发现`shapeType.triangle`等于哪个值并不重要,只要确保不会跟其他`shapeType`属性的值冲突即可。因此,这里就很适合改用 Symbol 值。 ```javascript const shapeType = { From fcdea67264949da25ad90e124016bf45c1348d76 Mon Sep 17 00:00:00 2001 From: Jacty Date: Sat, 22 Apr 2017 02:32:49 +0800 Subject: [PATCH 026/768] typo typo --- docs/function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/function.md b/docs/function.md index dc00bbf1e..490a179be 100644 --- a/docs/function.md +++ b/docs/function.md @@ -591,7 +591,7 @@ rest // [2, 3, 4, 5] const [first, ...rest] = []; first // undefined -rest // []: +rest // [] const [first, ...rest] = ["foo"]; first // "foo" From 4eb879872cbf7f3f9b3341d57155992568c79a27 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 22 Apr 2017 23:28:32 +0800 Subject: [PATCH 027/768] docs(reflect): edit reflect --- docs/reflect.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reflect.md b/docs/reflect.md index 7d1d211b8..26d33ece3 100644 --- a/docs/reflect.md +++ b/docs/reflect.md @@ -355,12 +355,12 @@ function MyDate() { // 旧写法 Object.defineProperty(MyDate, 'now', { - value: () => new Date.now() + value: () => Date.now() }); // 新写法 Reflect.defineProperty(MyDate, 'now', { - value: () => new Date.now() + value: () => Date.now() }); ``` From e06759e7efcea000d2bc39113b208c47dc48a2c8 Mon Sep 17 00:00:00 2001 From: Jacty Date: Tue, 25 Apr 2017 20:51:59 +0800 Subject: [PATCH 028/768] =?UTF-8?q?=E8=BF=99=E9=87=8C=E5=BA=94=E8=AF=A5?= =?UTF-8?q?=E6=98=AF=E8=AF=B4=E6=B5=85=E6=8B=B7=E8=B4=9D=E5=90=A7=EF=BC=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这里应该是说浅拷贝吧?前文说过Object.assign()是浅拷贝呀~ --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index d9d33a83d..701367423 100644 --- a/docs/object.md +++ b/docs/object.md @@ -566,7 +566,7 @@ function processContent(options) { 上面代码中,`DEFAULTS`对象是默认值,`options`对象是用户提供的参数。`Object.assign`方法将`DEFAULTS`和`options`合并成一个新对象,如果两者有同名属性,则`option`的属性值会覆盖`DEFAULTS`的属性值。 -注意,由于存在深拷贝的问题,`DEFAULTS`对象和`options`对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,`DEFAULTS`对象的该属性很可能不起作用。 +注意,由于存在浅拷贝的问题,`DEFAULTS`对象和`options`对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,`DEFAULTS`对象的该属性很可能不起作用。 ```javascript const DEFAULTS = { From fd9f4d6d00ce6ecaf59434fd68f8f1e7005d2969 Mon Sep 17 00:00:00 2001 From: Jacty Date: Tue, 25 Apr 2017 20:53:21 +0800 Subject: [PATCH 029/768] typo typo --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index d9d33a83d..714627bf4 100644 --- a/docs/object.md +++ b/docs/object.md @@ -652,7 +652,7 @@ ES6一共有5种方法可以遍历对象的属性。 **(5)Reflect.ownKeys(obj)** -`Reflect.ownKeys`返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。 +`Reflect.ownKeys`返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举。 以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。 From 34f321ec99f84314eb55dc52417dfb5102e880be Mon Sep 17 00:00:00 2001 From: Jacty Date: Tue, 25 Apr 2017 21:30:00 +0800 Subject: [PATCH 030/768] typo typo --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index d9d33a83d..0fe361c9e 100644 --- a/docs/object.md +++ b/docs/object.md @@ -1166,7 +1166,7 @@ let runtimeError = { }; ``` -如果扩展运算符的参数是`null`或`undefined`,这个两个值会被忽略,不会报错。 +如果扩展运算符的参数是`null`或`undefined`,这两个值会被忽略,不会报错。 ```javascript let emptyObject = { ...null, ...undefined }; // 不报错 From 7b81350aef68c79c108e5f5bc50d890accc2ada8 Mon Sep 17 00:00:00 2001 From: amisare <243297288@qq.com> Date: Wed, 26 Apr 2017 10:18:58 +0800 Subject: [PATCH 031/768] Update promise.md --- docs/promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/promise.md b/docs/promise.md index 256524198..18d796b5d 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -597,7 +597,7 @@ p.then(function (s){ // Hello ``` -上面代码生成一个新的Promise对象的实例`p`。由于字符串`Hello`不属于异步操作(判断方法是它不是具有then方法的对象),返回Promise实例的状态从一生成就是`Resolved`,所以回调函数会立即执行。`Promise.resolve`方法的参数,会同时传给回调函数。 +上面代码生成一个新的Promise对象的实例`p`。由于字符串`Hello`不属于异步操作(判断方法是字符串对象不具有then方法),返回Promise实例的状态从一生成就是`Resolved`,所以回调函数会立即执行。`Promise.resolve`方法的参数,会同时传给回调函数。 **(4)不带有任何参数** From f72cd63cdd631ac7b6b1240c62074be7de3a34dd Mon Sep 17 00:00:00 2001 From: lengjing <853625225@qq.com> Date: Wed, 26 Apr 2017 10:41:53 +0800 Subject: [PATCH 032/768] Update generator.md --- docs/generator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generator.md b/docs/generator.md index 2197c4857..1af51fc5c 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -221,7 +221,7 @@ g[Symbol.iterator]() === g ## next方法的参数 -`yield`句本身没有返回值,或者说总是返回`undefined`。`next`方法可以带一个参数,该参数就会被当作上一个`yield`语句的返回值。 +`yield`语句本身没有返回值,或者说总是返回`undefined`。`next`方法可以带一个参数,该参数就会被当作上一个`yield`语句的返回值。 ```javascript function* f() { From 3d280b452e7e1a1a1fa00cedf7dcea423234caaf Mon Sep 17 00:00:00 2001 From: amisare <243297288@qq.com> Date: Wed, 26 Apr 2017 11:01:30 +0800 Subject: [PATCH 033/768] Update array.md --- docs/array.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/array.md b/docs/array.md index 49097b510..33f5bd1be 100644 --- a/docs/array.md +++ b/docs/array.md @@ -70,7 +70,7 @@ function foo() { [...document.querySelectorAll('div')] ``` -扩展运算符背后调用的是遍历器接口(`Symbol.iterator`),如果一个对象没有部署这个接口,就无法转换。`Array.from`方法则是还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有`length`属性。因此,任何有`length`属性的对象,都可以通过`Array.from`方法转为数组,而此时扩展运算符就无法转换。 +扩展运算符背后调用的是遍历器接口(`Symbol.iterator`),如果一个对象没有部署这个接口,就无法转换。`Array.from`方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有`length`属性。因此,任何有`length`属性的对象,都可以通过`Array.from`方法转为数组,而此时扩展运算符就无法转换。 ```javascript Array.from({ length: 3 }); From e2867f1956b3f2c8838fce963748abfa4396b291 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 26 Apr 2017 23:03:25 +0800 Subject: [PATCH 034/768] docs(set): edit weakmap --- docs/generator-async.md | 7 +++---- docs/set-map.md | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/generator-async.md b/docs/generator-async.md index 6c0b7e9d3..e2c8ae216 100644 --- a/docs/generator-async.md +++ b/docs/generator-async.md @@ -320,7 +320,7 @@ var Thunk = function(fn){ }; // ES6版本 -var Thunk = function(fn) { +const Thunk = function(fn) { return function (...args) { return function (callback) { return fn.call(this, ...args, callback); @@ -342,10 +342,9 @@ readFileThunk(fileA)(callback); function f(a, cb) { cb(a); } -let ft = Thunk(f); +const ft = Thunk(f); -let log = console.log.bind(console); -ft(1)(log) // 1 +ft(1)(console.log) // 1 ``` ### Thunkify 模块 diff --git a/docs/set-map.md b/docs/set-map.md index d07c284b8..f373dd62a 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -1014,7 +1014,7 @@ undefined > let wm = new WeakMap(); undefined -> const b = new Object(); +> let b = new Object(); undefined > global.gc(); @@ -1053,7 +1053,7 @@ undefined // 解除 b 的引用以后,heapUsed 变回 4M 左右 // 说明 WeakMap 中的那个长度为 5*1024*1024 的数组被销毁了 -> process.memoryUsage(); +> process.memoryUsage(); { rss: 20639744, heapTotal: 8425472, heapUsed: 3979792, From 0cc0ce06bbabbfe07afe2275bcd1573e5416cf28 Mon Sep 17 00:00:00 2001 From: Jacty Date: Thu, 27 Apr 2017 01:13:38 +0800 Subject: [PATCH 035/768] typo typo --- docs/symbol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/symbol.md b/docs/symbol.md index a0744c183..9d80d87e6 100644 --- a/docs/symbol.md +++ b/docs/symbol.md @@ -196,7 +196,7 @@ function getComplement(color) { ## 实例:消除魔术字符串 -魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,该由含义清晰的变量代替。 +魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。 ```javascript function getArea(shape, options) { From 6867e870bce2fe8c34229ebd466b16e16a83b1d9 Mon Sep 17 00:00:00 2001 From: Jacty Date: Thu, 27 Apr 2017 02:18:55 +0800 Subject: [PATCH 036/768] typo typo --- docs/set-map.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/set-map.md b/docs/set-map.md index f373dd62a..954cdbf8a 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -788,7 +788,7 @@ const myMap = new Map() **(2)数组 转为 Map** -将数组转入 Map 构造函数,就可以转为 Map。 +将数组传入 Map 构造函数,就可以转为 Map。 ```javascript new Map([ From be1049f12ad7355424479be83f278aa95022d38d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 29 Apr 2017 06:33:43 +0800 Subject: [PATCH 037/768] docs(generator): edit typo --- docs/function.md | 41 +++++---- docs/generator.md | 207 +++++++++++++++++++++++----------------------- 2 files changed, 122 insertions(+), 126 deletions(-) diff --git a/docs/function.md b/docs/function.md index 490a179be..52f11d8bd 100644 --- a/docs/function.md +++ b/docs/function.md @@ -1228,7 +1228,7 @@ function f(x){ } ``` -上面代码中,函数f的最后一步是调用函数g,这就叫尾调用。 +上面代码中,函数`f`的最后一步是调用函数`g`,这就叫尾调用。 以下三种情况,都不属于尾调用。 @@ -1250,7 +1250,7 @@ function f(x){ } ``` -上面代码中,情况一是调用函数g之后,还有赋值操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。 +上面代码中,情况一是调用函数`g`之后,还有赋值操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。 ```javascript function f(x){ @@ -1270,13 +1270,13 @@ function f(x) { } ``` -上面代码中,函数m和n都属于尾调用,因为它们都是函数f的最后一步操作。 +上面代码中,函数`m`和`n`都属于尾调用,因为它们都是函数`f`的最后一步操作。 ### 尾调用优化 尾调用之所以与其他调用不同,就在于它的特殊的调用位置。 -我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。 +我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数`A`的内部调用函数`B`,那么在`A`的调用帧上方,还会形成一个`B`的调用帧。等到`B`运行结束,将结果返回到`A`,`B`的调用帧才会消失。如果函数`B`内部还调用函数`C`,那就还有一个`C`的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。 @@ -1298,7 +1298,7 @@ f(); g(3); ``` -上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除 f(x) 的调用帧,只保留 g(3) 的调用帧。 +上面代码中,如果函数`g`不是尾调用,函数`f`就需要保存内部变量`m`和`n`的值、`g`的调用位置等信息。但由于调用`g`之后,函数`f`就结束了,所以执行到最后一步,完全可以删除`f(x)`的调用帧,只保留`g(3)`的调用帧。 这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。 @@ -1331,7 +1331,7 @@ function factorial(n) { factorial(5) // 120 ``` -上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。 +上面代码是一个阶乘函数,计算`n`的阶乘,最多需要保存`n`个调用记录,复杂度 O(n) 。 如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。 @@ -1344,9 +1344,9 @@ function factorial(n, total) { factorial(5, 1) // 120 ``` -还有一个比较著名的例子,就是计算fibonacci 数列,也能充分说明尾递归优化的重要性 +还有一个比较著名的例子,就是计算 Fibonacci 数列,也能充分说明尾递归优化的重要性。 -如果是非尾递归的fibonacci 递归方法 +非尾递归的 Fibonacci 数列实现如下。 ```javascript function Fibonacci (n) { @@ -1355,13 +1355,12 @@ function Fibonacci (n) { return Fibonacci(n - 1) + Fibonacci(n - 2); } -Fibonacci(10); // 89 -// Fibonacci(100) -// Fibonacci(500) -// 堆栈溢出了 +Fibonacci(10) // 89 +Fibonacci(100) // 堆栈溢出 +Fibonacci(500) // 堆栈溢出 ``` -如果我们使用尾递归优化过的fibonacci 递归算法 +尾递归优化过的 Fibonacci 数列实现如下。 ```javascript function Fibonacci2 (n , ac1 = 1 , ac2 = 1) { @@ -1375,11 +1374,11 @@ Fibonacci2(1000) // 7.0330367711422765e+208 Fibonacci2(10000) // Infinity ``` -由此可见,“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有ECMAScript的实现,都必须部署“尾调用优化”。这就是说,在ES6中,只要使用尾递归,就不会发生栈溢出,相对节省内存。 +由此可见,“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6 是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。这就是说,ES6 中只要使用尾递归,就不会发生栈溢出,相对节省内存。 ### 递归函数的改写 -尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。比如上面的例子,阶乘函数 factorial 需要用到一个中间变量 total ,那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观,第一眼很难看出来,为什么计算5的阶乘,需要传入两个参数5和1? +尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。比如上面的例子,阶乘函数 factorial 需要用到一个中间变量`total`,那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观,第一眼很难看出来,为什么计算`5`的阶乘,需要传入两个参数`5`和`1`? 两个方法可以解决这个问题。方法一是在尾递归函数之外,再提供一个正常形式的函数。 @@ -1396,7 +1395,7 @@ function factorial(n) { factorial(5) // 120 ``` -上面代码通过一个正常形式的阶乘函数 factorial ,调用尾递归函数 tailFactorial ,看起来就正常多了。 +上面代码通过一个正常形式的阶乘函数`factorial`,调用尾递归函数`tailFactorial`,看起来就正常多了。 函数式编程有一个概念,叫做柯里化(currying),意思是将多参数的函数转换成单参数的形式。这里也可以使用柯里化。 @@ -1417,9 +1416,9 @@ const factorial = currying(tailFactorial, 1); factorial(5) // 120 ``` -上面代码通过柯里化,将尾递归函数 tailFactorial 变为只接受1个参数的 factorial 。 +上面代码通过柯里化,将尾递归函数`tailFactorial`变为只接受一个参数的`factorial`。 -第二种方法就简单多了,就是采用ES6的函数默认值。 +第二种方法就简单多了,就是采用 ES6 的函数默认值。 ```javascript function factorial(n, total = 1) { @@ -1430,13 +1429,13 @@ function factorial(n, total = 1) { factorial(5) // 120 ``` -上面代码中,参数 total 有默认值1,所以调用时不用提供这个值。 +上面代码中,参数`total`有默认值`1`,所以调用时不用提供这个值。 总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。 ### 严格模式 -ES6的尾调用优化只在严格模式下开启,正常模式是无效的。 +ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。 这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。 @@ -1447,7 +1446,7 @@ ES6的尾调用优化只在严格模式下开启,正常模式是无效的。 ```javascript function restricted() { - "use strict"; + 'use strict'; restricted.caller; // 报错 restricted.arguments; // 报错 } diff --git a/docs/generator.md b/docs/generator.md index 1af51fc5c..209c8acdf 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -4,13 +4,13 @@ ### 基本概念 -Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。本章详细介绍Generator 函数的语法和 API,它的异步编程应用请看《Generator 函数的异步应用》一章。 +Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。本章详细介绍 Generator 函数的语法和 API,它的异步编程应用请看《Generator 函数的异步应用》一章。 Generator 函数有多种理解角度。从语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。 -形式上,Generator 函数是一个普通函数,但是有两个特征。一是,`function`关键字与函数名之间有一个星号;二是,函数体内部使用`yield`语句,定义不同的内部状态(`yield`在英语里的意思就是“产出”)。 +形式上,Generator 函数是一个普通函数,但是有两个特征。一是,`function`关键字与函数名之间有一个星号;二是,函数体内部使用`yield`表达式,定义不同的内部状态(`yield`在英语里的意思就是“产出”)。 ```javascript function* helloWorldGenerator() { @@ -22,11 +22,11 @@ function* helloWorldGenerator() { var hw = helloWorldGenerator(); ``` -上面代码定义了一个Generator函数`helloWorldGenerator`,它内部有两个`yield`语句“hello”和“world”,即该函数有三个状态:hello,world和return语句(结束执行)。 +上面代码定义了一个 Generator 函数`helloWorldGenerator`,它内部有两个`yield`表达式(`hello`和`world`),即该函数有三个状态:hello,world 和 return 语句(结束执行)。 -然后,Generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。 +然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。 -下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用`next`方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个`yield`语句(或`return`语句)为止。换言之,Generator函数是分段执行的,`yield`语句是暂停执行的标记,而`next`方法可以恢复执行。 +下一步,必须调用遍历器对象的`next`方法,使得指针移向下一个状态。也就是说,每次调用`next`方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个`yield`表达式(或`return`语句)为止。换言之,Generator 函数是分段执行的,`yield`表达式是暂停执行的标记,而`next`方法可以恢复执行。 ```javascript hw.next() @@ -44,45 +44,42 @@ hw.next() 上面代码一共调用了四次`next`方法。 -第一次调用,Generator函数开始执行,直到遇到第一个`yield`语句为止。`next`方法返回一个对象,它的`value`属性就是当前`yield`语句的值hello,`done`属性的值false,表示遍历还没有结束。 +第一次调用,Generator 函数开始执行,直到遇到第一个`yield`表达式为止。`next`方法返回一个对象,它的`value`属性就是当前`yield`表达式的值`hello`,`done`属性的值`false`,表示遍历还没有结束。 -第二次调用,Generator函数从上次`yield`语句停下的地方,一直执行到下一个`yield`语句。`next`方法返回的对象的`value`属性就是当前`yield`语句的值world,`done`属性的值false,表示遍历还没有结束。 +第二次调用,Generator 函数从上次`yield`表达式停下的地方,一直执行到下一个`yield`表达式。`next`方法返回的对象的`value`属性就是当前`yield`表达式的值`world`,`done`属性的值`false`,表示遍历还没有结束。 -第三次调用,Generator函数从上次`yield`语句停下的地方,一直执行到`return`语句(如果没有return语句,就执行到函数结束)。`next`方法返回的对象的`value`属性,就是紧跟在`return`语句后面的表达式的值(如果没有`return`语句,则`value`属性的值为undefined),`done`属性的值true,表示遍历已经结束。 +第三次调用,Generator 函数从上次`yield`表达式停下的地方,一直执行到`return`语句(如果没有`return`语句,就执行到函数结束)。`next`方法返回的对象的`value`属性,就是紧跟在`return`语句后面的表达式的值(如果没有`return`语句,则`value`属性的值为`undefined`),`done`属性的值`true`,表示遍历已经结束。 -第四次调用,此时Generator函数已经运行完毕,`next`方法返回对象的`value`属性为undefined,`done`属性为true。以后再调用`next`方法,返回的都是这个值。 +第四次调用,此时 Generator 函数已经运行完毕,`next`方法返回对象的`value`属性为`undefined`,`done`属性为`true`。以后再调用`next`方法,返回的都是这个值。 -总结一下,调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的`next`方法,就会返回一个有着`value`和`done`两个属性的对象。`value`属性表示当前的内部状态的值,是`yield`语句后面那个表达式的值;`done`属性是一个布尔值,表示是否遍历结束。 +总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的`next`方法,就会返回一个有着`value`和`done`两个属性的对象。`value`属性表示当前的内部状态的值,是`yield`表达式后面那个表达式的值;`done`属性是一个布尔值,表示是否遍历结束。 -ES6没有规定,`function`关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。 +ES6 没有规定,`function`关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。 ```javascript function * foo(x, y) { ··· } - function *foo(x, y) { ··· } - function* foo(x, y) { ··· } - function*foo(x, y) { ··· } ``` -由于Generator函数仍然是普通函数,所以一般的写法是上面的第三种,即星号紧跟在`function`关键字后面。本书也采用这种写法。 +由于 Generator 函数仍然是普通函数,所以一般的写法是上面的第三种,即星号紧跟在`function`关键字后面。本书也采用这种写法。 -### yield语句 +### yield 表达式 -由于Generator函数返回的遍历器对象,只有调用`next`方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。`yield`语句就是暂停标志。 +由于 Generator 函数返回的遍历器对象,只有调用`next`方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。`yield`表达式就是暂停标志。 遍历器对象的`next`方法的运行逻辑如下。 -(1)遇到`yield`语句,就暂停执行后面的操作,并将紧跟在`yield`后面的那个表达式的值,作为返回的对象的`value`属性值。 +(1)遇到`yield`表达式,就暂停执行后面的操作,并将紧跟在`yield`后面的那个表达式的值,作为返回的对象的`value`属性值。 -(2)下一次调用`next`方法时,再继续往下执行,直到遇到下一个`yield`语句。 +(2)下一次调用`next`方法时,再继续往下执行,直到遇到下一个`yield`表达式。 -(3)如果没有再遇到新的`yield`语句,就一直运行到函数结束,直到`return`语句为止,并将`return`语句后面的表达式的值,作为返回的对象的`value`属性值。 +(3)如果没有再遇到新的`yield`表达式,就一直运行到函数结束,直到`return`语句为止,并将`return`语句后面的表达式的值,作为返回的对象的`value`属性值。 (4)如果该函数没有`return`语句,则返回的对象的`value`属性值为`undefined`。 -需要注意的是,`yield`语句后面的表达式,只有当调用`next`方法、内部指针指向该语句时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。 +需要注意的是,`yield`表达式后面的表达式,只有当调用`next`方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。 ```javascript function* gen() { @@ -90,11 +87,11 @@ function* gen() { } ``` -上面代码中,yield后面的表达式`123 + 456`,不会立即求值,只会在`next`方法将指针移到这一句时,才会求值。 +上面代码中,`yield`后面的表达式`123 + 456`,不会立即求值,只会在`next`方法将指针移到这一句时,才会求值。 -`yield`语句与`return`语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到`yield`,函数暂停执行,下一次再从该位置继续向后执行,而`return`语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)`return`语句,但是可以执行多次(或者说多个)`yield`语句。正常函数只能返回一个值,因为只能执行一次`return`;Generator函数可以返回一系列的值,因为可以有任意多个`yield`。从另一个角度看,也可以说Generator生成了一系列的值,这也就是它的名称的来历(在英语中,generator这个词是“生成器”的意思)。 +`yield`表达式与`return`语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到`yield`,函数暂停执行,下一次再从该位置继续向后执行,而`return`语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)`return`语句,但是可以执行多次(或者说多个)`yield`表达式。正常函数只能返回一个值,因为只能执行一次`return`;Generator 函数可以返回一系列的值,因为可以有任意多个`yield`。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。 -Generator函数可以不用`yield`语句,这时就变成了一个单纯的暂缓执行函数。 +Generator 函数可以不用`yield`表达式,这时就变成了一个单纯的暂缓执行函数。 ```javascript function* f() { @@ -110,7 +107,7 @@ setTimeout(function () { 上面代码中,函数`f`如果是普通函数,在为变量`generator`赋值时就会执行。但是,函数`f`是一个 Generator 函数,就变成只有调用`next`方法时,函数`f`才会执行。 -另外需要注意,`yield`语句只能用在 Generator 函数里面,用在其他地方都会报错。 +另外需要注意,`yield`表达式只能用在 Generator 函数里面,用在其他地方都会报错。 ```javascript (function (){ @@ -119,7 +116,7 @@ setTimeout(function () { // SyntaxError: Unexpected number ``` -上面代码在一个普通函数中使用`yield`语句,结果产生一个句法错误。 +上面代码在一个普通函数中使用`yield`表达式,结果产生一个句法错误。 下面是另外一个例子。 @@ -141,7 +138,7 @@ for (var f of flat(arr)){ } ``` -上面代码也会产生句法错误,因为`forEach`方法的参数是一个普通函数,但是在里面使用了`yield`语句(这个函数里面还使用了`yield*`语句,详细介绍见后文)。一种修改方法是改用`for`循环。 +上面代码也会产生句法错误,因为`forEach`方法的参数是一个普通函数,但是在里面使用了`yield`表达式(这个函数里面还使用了`yield*`表达式,详细介绍见后文)。一种修改方法是改用`for`循环。 ```javascript var arr = [1, [[2, 3], 4], [5, 6]]; @@ -164,7 +161,7 @@ for (var f of flat(arr)) { // 1, 2, 3, 4, 5, 6 ``` -另外,`yield`语句如果用在一个表达式之中,必须放在圆括号里面。 +另外,`yield`表达式如果用在另一个表达式之中,必须放在圆括号里面。 ```javascript function* demo() { @@ -176,7 +173,7 @@ function* demo() { } ``` -`yield`语句用作函数参数或放在赋值表达式的右边,可以不加括号。 +`yield`表达式用作函数参数或放在赋值表达式的右边,可以不加括号。 ```javascript function* demo() { @@ -189,7 +186,7 @@ function* demo() { 上一章说过,任意一个对象的`Symbol.iterator`方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。 -由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的`Symbol.iterator`属性,从而使得该对象具有Iterator接口。 +由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的`Symbol.iterator`属性,从而使得该对象具有 Iterator 接口。 ```javascript var myIterable = {}; @@ -202,9 +199,9 @@ myIterable[Symbol.iterator] = function* () { [...myIterable] // [1, 2, 3] ``` -上面代码中,Generator函数赋值给`Symbol.iterator`属性,从而使得`myIterable`对象具有了Iterator接口,可以被`...`运算符遍历了。 +上面代码中,Generator 函数赋值给`Symbol.iterator`属性,从而使得`myIterable`对象具有了 Iterator 接口,可以被`...`运算符遍历了。 -Generator函数执行后,返回一个遍历器对象。该对象本身也具有`Symbol.iterator`属性,执行后返回自身。 +Generator 函数执行后,返回一个遍历器对象。该对象本身也具有`Symbol.iterator`属性,执行后返回自身。 ```javascript function* gen(){ @@ -217,11 +214,11 @@ g[Symbol.iterator]() === g // true ``` -上面代码中,`gen`是一个Generator函数,调用它会生成一个遍历器对象`g`。它的`Symbol.iterator`属性,也是一个遍历器对象生成函数,执行后返回它自己。 +上面代码中,`gen`是一个 Generator 函数,调用它会生成一个遍历器对象`g`。它的`Symbol.iterator`属性,也是一个遍历器对象生成函数,执行后返回它自己。 -## next方法的参数 +## next 方法的参数 -`yield`语句本身没有返回值,或者说总是返回`undefined`。`next`方法可以带一个参数,该参数就会被当作上一个`yield`语句的返回值。 +`yield`表达式本身没有返回值,或者说总是返回`undefined`。`next`方法可以带一个参数,该参数就会被当作上一个`yield`表达式的返回值。 ```javascript function* f() { @@ -238,7 +235,7 @@ g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false } ``` -上面代码先定义了一个可以无限运行的 Generator 函数`f`,如果`next`方法没有参数,每次运行到`yield`语句,变量`reset`的值总是`undefined`。当`next`方法带一个参数`true`时,变量`reset`就被重置为这个参数(即`true`),因此`i`会等于`-1`,下一轮循环就会从`-1`开始递增。 +上面代码先定义了一个可以无限运行的 Generator 函数`f`,如果`next`方法没有参数,每次运行到`yield`表达式,变量`reset`的值总是`undefined`。当`next`方法带一个参数`true`时,变量`reset`就被重置为这个参数(即`true`),因此`i`会等于`-1`,下一轮循环就会从`-1`开始递增。 这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过`next`方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。 @@ -264,11 +261,11 @@ b.next(13) // { value:42, done:true } 上面代码中,第二次运行`next`方法的时候不带参数,导致y的值等于`2 * undefined`(即`NaN`),除以3以后还是`NaN`,因此返回对象的`value`属性也等于`NaN`。第三次运行`Next`方法的时候不带参数,所以`z`等于`undefined`,返回对象的`value`属性等于`5 + NaN + undefined`,即`NaN`。 -如果向`next`方法提供参数,返回结果就完全不一样了。上面代码第一次调用`b`的`next`方法时,返回`x+1`的值6;第二次调用`next`方法,将上一次`yield`语句的值设为12,因此`y`等于24,返回`y / 3`的值8;第三次调用`next`方法,将上一次`yield`语句的值设为13,因此`z`等于13,这时`x`等于5,`y`等于24,所以`return`语句的值等于42。 +如果向`next`方法提供参数,返回结果就完全不一样了。上面代码第一次调用`b`的`next`方法时,返回`x+1`的值`6`;第二次调用`next`方法,将上一次`yield`表达式的值设为`12`,因此`y`等于`24`,返回`y / 3`的值`8`;第三次调用`next`方法,将上一次`yield`表达式的值设为`13`,因此`z`等于`13`,这时`x`等于`5`,`y`等于`24`,所以`return`语句的值等于`42`。 -注意,由于`next`方法的参数表示上一个`yield`语句的返回值,所以第一次使用`next`方法时,不能带有参数。V8引擎直接忽略第一次使用`next`方法时的参数,只有从第二次使用`next`方法开始,参数才是有效的。从语义上讲,第一个`next`方法用来启动遍历器对象,所以不用带有参数。 +注意,由于`next`方法的参数表示上一个`yield`表达式的返回值,所以第一次使用`next`方法时,不能带有参数。V8 引擎直接忽略第一次使用`next`方法时的参数,只有从第二次使用`next`方法开始,参数才是有效的。从语义上讲,第一个`next`方法用来启动遍历器对象,所以不用带有参数。 -如果想要第一次调用`next`方法时,就能够输入值,可以在Generator函数外面再包一层。 +如果想要第一次调用`next`方法时,就能够输入值,可以在 Generator 函数外面再包一层。 ```javascript function wrapper(generatorFunction) { @@ -288,9 +285,9 @@ wrapped().next('hello!') // First input: hello! ``` -上面代码中,Generator函数如果不用`wrapper`先包一层,是无法第一次调用`next`方法,就输入参数的。 +上面代码中,Generator 函数如果不用`wrapper`先包一层,是无法第一次调用`next`方法,就输入参数的。 -再看一个通过`next`方法的参数,向Generator函数内部输入值的例子。 +再看一个通过`next`方法的参数,向 Generator 函数内部输入值的例子。 ```javascript function* dataConsumer() { @@ -309,11 +306,11 @@ genObj.next('b') // 2. b ``` -上面代码是一个很直观的例子,每次通过`next`方法向Generator函数输入值,然后打印出来。 +上面代码是一个很直观的例子,每次通过`next`方法向 Generator 函数输入值,然后打印出来。 -## for...of循环 +## for...of 循环 -`for...of`循环可以自动遍历Generator函数时生成的`Iterator`对象,且此时不再需要调用`next`方法。 +`for...of`循环可以自动遍历 Generator 函数时生成的`Iterator`对象,且此时不再需要调用`next`方法。 ```javascript function *foo() { @@ -331,9 +328,9 @@ for (let v of foo()) { // 1 2 3 4 5 ``` -上面代码使用`for...of`循环,依次显示5个`yield`语句的值。这里需要注意,一旦`next`方法的返回对象的`done`属性为`true`,`for...of`循环就会中止,且不包含该返回对象,所以上面代码的`return`语句返回的6,不包括在`for...of`循环之中。 +上面代码使用`for...of`循环,依次显示5个`yield`表达式的值。这里需要注意,一旦`next`方法的返回对象的`done`属性为`true`,`for...of`循环就会中止,且不包含该返回对象,所以上面代码的`return`语句返回的`6`,不包括在`for...of`循环之中。 -下面是一个利用Generator函数和`for...of`循环,实现斐波那契数列的例子。 +下面是一个利用 Generator 函数和`for...of`循环,实现斐波那契数列的例子。 ```javascript function* fibonacci() { @@ -352,7 +349,7 @@ for (let n of fibonacci()) { 从上面代码可见,使用`for...of`语句时不需要使用`next`方法。 -利用`for...of`循环,可以写出遍历任意对象(object)的方法。原生的JavaScript对象没有遍历接口,无法使用`for...of`循环,通过Generator函数为它加上这个接口,就可以用了。 +利用`for...of`循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有遍历接口,无法使用`for...of`循环,通过 Generator 函数为它加上这个接口,就可以用了。 ```javascript function* objectEntries(obj) { @@ -372,7 +369,7 @@ for (let [key, value] of objectEntries(jane)) { // last: Doe ``` -上面代码中,对象`jane`原生不具备Iterator接口,无法用`for...of`遍历。这时,我们通过Generator函数`objectEntries`为它加上遍历器接口,就可以用`for...of`遍历了。加上遍历器接口的另一种写法是,将Generator函数加到对象的`Symbol.iterator`属性上面。 +上面代码中,对象`jane`原生不具备 Iterator 接口,无法用`for...of`遍历。这时,我们通过 Generator 函数`objectEntries`为它加上遍历器接口,就可以用`for...of`遍历了。加上遍历器接口的另一种写法是,将 Generator 函数加到对象的`Symbol.iterator`属性上面。 ```javascript function* objectEntries() { @@ -394,7 +391,7 @@ for (let [key, value] of jane) { // last: Doe ``` -除了`for...of`循环以外,扩展运算符(`...`)、解构赋值和`Array.from`方法内部调用的,都是遍历器接口。这意味着,它们都可以将Generator函数返回的Iterator对象,作为参数。 +除了`for...of`循环以外,扩展运算符(`...`)、解构赋值和`Array.from`方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。 ```javascript function* numbers () { @@ -425,7 +422,7 @@ for (let n of numbers()) { ## Generator.prototype.throw() -Generator函数返回的遍历器对象,都有一个`throw`方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。 +Generator 函数返回的遍历器对象,都有一个`throw`方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。 ```javascript var g = function* () { @@ -449,7 +446,7 @@ try { // 外部捕获 b ``` -上面代码中,遍历器对象`i`连续抛出两个错误。第一个错误被Generator函数体内的`catch`语句捕获。`i`第二次抛出错误,由于Generator函数内部的`catch`语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了Generator函数体,被函数体外的`catch`语句捕获。 +上面代码中,遍历器对象`i`连续抛出两个错误。第一个错误被 Generator 函数体内的`catch`语句捕获。`i`第二次抛出错误,由于 Generator 函数内部的`catch`语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的`catch`语句捕获。 `throw`方法可以接受一个参数,该参数会被`catch`语句接收,建议抛出`Error`对象的实例。 @@ -496,7 +493,7 @@ try { 上面代码之所以只捕获了`a`,是因为函数体外的`catch`语句块,捕获了抛出的`a`错误以后,就不会再继续`try`代码块里面剩余的语句了。 -如果Generator函数内部没有部署`try...catch`代码块,那么`throw`方法抛出的错误,将被外部`try...catch`代码块捕获。 +如果 Generator 函数内部没有部署`try...catch`代码块,那么`throw`方法抛出的错误,将被外部`try...catch`代码块捕获。 ```javascript var g = function* () { @@ -518,9 +515,9 @@ try { // 外部捕获 a ``` -上面代码中,Generator函数`g`内部没有部署`try...catch`代码块,所以抛出的错误直接被外部`catch`代码块捕获。 +上面代码中,Generator 函数`g`内部没有部署`try...catch`代码块,所以抛出的错误直接被外部`catch`代码块捕获。 -如果Generator函数内部和外部,都没有部署`try...catch`代码块,那么程序将报错,直接中断执行。 +如果 Generator 函数内部和外部,都没有部署`try...catch`代码块,那么程序将报错,直接中断执行。 ```javascript var gen = function* gen(){ @@ -537,7 +534,7 @@ g.throw(); 上面代码中,`g.throw`抛出错误以后,没有任何`try...catch`代码块可以捕获这个错误,导致程序报错,中断执行。 -`throw`方法被捕获以后,会附带执行下一条`yield`语句。也就是说,会附带执行一次`next`方法。 +`throw`方法被捕获以后,会附带执行下一条`yield`表达式。也就是说,会附带执行一次`next`方法。 ```javascript var gen = function* gen(){ @@ -556,7 +553,7 @@ g.throw() // b g.next() // c ``` -上面代码中,`g.throw`方法被捕获以后,自动执行了一次`next`方法,所以会打印`b`。另外,也可以看到,只要Generator函数内部部署了`try...catch`代码块,那么遍历器的`throw`方法抛出的错误,不影响下一次遍历。 +上面代码中,`g.throw`方法被捕获以后,自动执行了一次`next`方法,所以会打印`b`。另外,也可以看到,只要 Generator 函数内部部署了`try...catch`代码块,那么遍历器的`throw`方法抛出的错误,不影响下一次遍历。 另外,`throw`命令与`g.throw`方法是无关的,两者互不影响。 @@ -580,9 +577,9 @@ try { 上面代码中,`throw`命令抛出的错误不会影响到遍历器的状态,所以两次执行`next`方法,都进行了正确的操作。 -这种函数体内捕获错误的机制,大大方便了对错误的处理。多个`yield`语句,可以只用一个`try...catch`代码块来捕获错误。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数内部写一个错误处理语句,现在只在Generator函数内部写一次`catch`语句就可以了。 +这种函数体内捕获错误的机制,大大方便了对错误的处理。多个`yield`表达式,可以只用一个`try...catch`代码块来捕获错误。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数内部写一个错误处理语句,现在只在 Generator 函数内部写一次`catch`语句就可以了。 -Generator函数体外抛出的错误,可以在函数体内捕获;反过来,Generator函数体内抛出的错误,也可以被函数体外的`catch`捕获。 +Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的`catch`捕获。 ```javascript function* foo() { @@ -604,7 +601,7 @@ try { 上面代码中,第二个`next`方法向函数体内传入一个参数42,数值是没有`toUpperCase`方法的,所以会抛出一个TypeError错误,被函数体外的`catch`捕获。 -一旦Generator执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用`next`方法,将返回一个`value`属性等于`undefined`、`done`属性等于`true`的对象,即JavaScript引擎认为这个Generator已经运行结束了。 +一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用`next`方法,将返回一个`value`属性等于`undefined`、`done`属性等于`true`的对象,即 JavaScript 引擎认为这个 Generator 已经运行结束了。 ```javascript function* g() { @@ -648,7 +645,7 @@ log(g()); // caller done ``` -上面代码一共三次运行`next`方法,第二次运行的时候会抛出错误,然后第三次运行的时候,Generator函数就已经结束了,不再执行下去了。 +上面代码一共三次运行`next`方法,第二次运行的时候会抛出错误,然后第三次运行的时候,Generator 函数就已经结束了,不再执行下去了。 ## Generator.prototype.return() @@ -685,7 +682,7 @@ g.next() // { value: 1, done: false } g.return() // { value: undefined, done: true } ``` -如果Generator函数内部有`try...finally`代码块,那么`return`方法会推迟到`finally`代码块执行完再执行。 +如果 Generator 函数内部有`try...finally`代码块,那么`return`方法会推迟到`finally`代码块执行完再执行。 ```javascript function* numbers () { @@ -709,7 +706,7 @@ g.next() // { value: 7, done: true } 上面代码中,调用`return`方法后,就开始执行`finally`代码块,然后等到`finally`代码块执行完,再执行`return`方法。 -## yield* 语句 +## yield* 表达式 如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。 @@ -734,7 +731,7 @@ for (let v of bar()){ 上面代码中,`foo`和`bar`都是 Generator 函数,在`bar`里面调用`foo`,是不会有效果的。 -这个就需要用到`yield*`语句,用来在一个 Generator 函数里面执行另一个 Generator 函数。 +这个就需要用到`yield*`表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。 ```javascript function* bar() { @@ -801,7 +798,7 @@ gen.next().value // "close" 上面例子中,`outer2`使用了`yield*`,`outer1`没使用。结果就是,`outer1`返回一个遍历器对象,`outer2`返回该遍历器对象的内部值。 -从语法角度看,如果`yield`命令后面跟的是一个遍历器对象,需要在`yield`命令后面加上星号,表明它返回的是一个遍历器对象。这被称为`yield*`语句。 +从语法角度看,如果`yield`表达式后面跟的是一个遍历器对象,需要在`yield`表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为`yield*`表达式。 ```javascript let delegatedIterator = (function* () { @@ -826,7 +823,7 @@ for(let value of delegatingIterator) { 上面代码中,`delegatingIterator`是代理者,`delegatedIterator`是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Generator函数,有递归的效果。 -`yield*`后面的Generator函数(没有`return`语句时),等同于在Generator函数内部,部署一个`for...of`循环。 +`yield*`后面的 Generator 函数(没有`return`语句时),等同于在 Generator 函数内部,部署一个`for...of`循环。 ```javascript function* concat(iter1, iter2) { @@ -860,7 +857,7 @@ gen().next() // { value:"a", done:false } 上面代码中,`yield`命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。 -实际上,任何数据结构只要有Iterator接口,就可以被`yield*`遍历。 +实际上,任何数据结构只要有 Iterator 接口,就可以被`yield*`遍历。 ```javascript let read = (function* () { @@ -872,9 +869,9 @@ read.next().value // "hello" read.next().value // "h" ``` -上面代码中,`yield`语句返回整个字符串,`yield*`语句返回单个字符。因为字符串具有Iterator接口,所以被`yield*`遍历。 +上面代码中,`yield`表达式返回整个字符串,`yield*`语句返回单个字符。因为字符串具有 Iterator 接口,所以被`yield*`遍历。 -如果被代理的Generator函数有`return`语句,那么就可以向代理它的Generator函数返回数据。 +如果被代理的 Generator 函数有`return`语句,那么就可以向代理它的 Generator 函数返回数据。 ```javascript function *foo() { @@ -994,7 +991,7 @@ result ## 作为对象属性的Generator函数 -如果一个对象的属性是Generator函数,可以简写成下面的形式。 +如果一个对象的属性是 Generator 函数,可以简写成下面的形式。 ```javascript let obj = { @@ -1004,7 +1001,7 @@ let obj = { }; ``` -上面代码中,`myGeneratorMethod`属性前面有一个星号,表示这个属性是一个Generator函数。 +上面代码中,`myGeneratorMethod`属性前面有一个星号,表示这个属性是一个 Generator 函数。 它的完整形式如下,与上面的写法是等价的。 @@ -1016,9 +1013,9 @@ let obj = { }; ``` -## Generator函数的`this` +## Generator 函数的`this` -Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,也继承了Generator函数的`prototype`对象上的方法。 +Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的`prototype`对象上的方法。 ```javascript function* g() {} @@ -1033,7 +1030,7 @@ obj instanceof g // true obj.hello() // 'hi!' ``` -上面代码表明,Generator函数`g`返回的遍历器`obj`,是`g`的实例,而且继承了`g.prototype`。但是,如果把`g`当作普通的构造函数,并不会生效,因为`g`返回的总是遍历器对象,而不是`this`对象。 +上面代码表明,Generator 函数`g`返回的遍历器`obj`,是`g`的实例,而且继承了`g.prototype`。但是,如果把`g`当作普通的构造函数,并不会生效,因为`g`返回的总是遍历器对象,而不是`this`对象。 ```javascript function* g() { @@ -1060,9 +1057,9 @@ new F() 上面代码中,`new`命令跟构造函数`F`一起使用,结果报错,因为`F`不是构造函数。 -那么,有没有办法让Generator函数返回一个正常的对象实例,既可以用`next`方法,又可以获得正常的`this`? +那么,有没有办法让 Generator 函数返回一个正常的对象实例,既可以用`next`方法,又可以获得正常的`this`? -下面是一个变通方法。首先,生成一个空对象,使用`call`方法绑定Generator函数内部的`this`。这样,构造函数调用以后,这个空对象就是Generator函数的实例对象了。 +下面是一个变通方法。首先,生成一个空对象,使用`call`方法绑定 Generator 函数内部的`this`。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。 ```javascript function* F() { @@ -1082,7 +1079,7 @@ obj.b // 2 obj.c // 3 ``` -上面代码中,首先是`F`内部的`this`对象绑定`obj`对象,然后调用它,返回一个Iterator对象。这个对象执行三次`next`方法(因为`F`内部有两个`yield`语句),完成F内部所有代码的运行。这时,所有内部属性都绑定在`obj`对象上了,因此`obj`对象也就成了`F`的实例。 +上面代码中,首先是`F`内部的`this`对象绑定`obj`对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次`next`方法(因为`F`内部有两个`yield`表达式),完成F内部所有代码的运行。这时,所有内部属性都绑定在`obj`对象上了,因此`obj`对象也就成了`F`的实例。 上面代码中,执行的是遍历器对象`f`,但是生成的对象实例是`obj`,有没有办法将这两个对象统一呢? @@ -1131,9 +1128,9 @@ f.c // 3 ## 含义 -### Generator与状态机 +### Generator 与状态机 -Generator是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。 +Generator 是实现状态机的最佳结构。比如,下面的`clock`函数就是一个状态机。 ```javascript var ticking = true; @@ -1146,10 +1143,10 @@ var clock = function() { } ``` -上面代码的clock函数一共有两种状态(Tick和Tock),每运行一次,就改变一次状态。这个函数如果用Generator实现,就是下面这样。 +上面代码的`clock`函数一共有两种状态(`Tick`和`Tock`),每运行一次,就改变一次状态。这个函数如果用 Generator 实现,就是下面这样。 ```javascript -var clock = function*() { +var clock = function* () { while (true) { console.log('Tick!'); yield; @@ -1159,7 +1156,7 @@ var clock = function*() { }; ``` -上面的Generator实现与ES5实现对比,可以看到少了用来保存状态的外部变量`ticking`,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。 +上面的 Generator 实现与 ES5 实现对比,可以看到少了用来保存状态的外部变量`ticking`,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator 之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。 ### Generator与协程 @@ -1175,19 +1172,19 @@ var clock = function*() { 不难看出,协程适合用于多任务运行的环境。在这个意义上,它与普通的线程很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态。此外,普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。 -由于ECMAScript是单线程语言,只能保持一个调用栈。引入协程以后,每个任务可以保持自己的调用栈。这样做的最大好处,就是抛出错误的时候,可以找到原始的调用栈。不至于像异步操作的回调函数那样,一旦出错,原始的调用栈早就结束。 +由于 JavaScript 是单线程语言,只能保持一个调用栈。引入协程以后,每个任务可以保持自己的调用栈。这样做的最大好处,就是抛出错误的时候,可以找到原始的调用栈。不至于像异步操作的回调函数那样,一旦出错,原始的调用栈早就结束。 -Generator函数是ECMAScript 6对协程的实现,但属于不完全实现。Generator函数被称为“半协程”(semi-coroutine),意思是只有Generator函数的调用者,才能将程序的执行权还给Generator函数。如果是完全执行的协程,任何函数都可以让暂停的协程继续执行。 +Generator 函数是 ES6 对协程的实现,但属于不完全实现。Generator 函数被称为“半协程”(semi-coroutine),意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。如果是完全执行的协程,任何函数都可以让暂停的协程继续执行。 -如果将Generator函数当作协程,完全可以将多个需要互相协作的任务写成Generator函数,它们之间使用yield语句交换控制权。 +如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用`yield`表示式交换控制权。 ## 应用 -Generator可以暂停函数执行,返回任意表达式的值。这种特点使得Generator有多种应用场景。 +Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景。 ### (1)异步操作的同步化表达 -Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。 +Generator 函数的暂停执行的效果,意味着可以把异步操作写在`yield`表达式里面,等到调用`next`方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在`yield`表达式下面,反正要等到调用`next`方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数。 ```javascript function* loadUI() { @@ -1203,9 +1200,9 @@ loader.next() loader.next() ``` -上面代码表示,第一次调用loadUI函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next方法,则会显示Loading界面,并且异步加载数据。等到数据加载完成,再一次使用next方法,则会隐藏Loading界面。可以看到,这种写法的好处是所有Loading界面的逻辑,都被封装在一个函数,按部就班非常清晰。 +上面代码中,第一次调用`loadUI`函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用`next`方法,则会显示`Loading`界面(`showLoadingScreen`),并且异步加载数据(`loadUIDataAsynchronously`)。等到数据加载完成,再一次使用`next`方法,则会隐藏`Loading`界面。可以看到,这种写法的好处是所有`Loading`界面的逻辑,都被封装在一个函数,按部就班非常清晰。 -Ajax是典型的异步操作,通过Generator函数部署Ajax操作,可以用同步的方式表达。 +Ajax 是典型的异步操作,通过 Generator 函数部署 Ajax 操作,可以用同步的方式表达。 ```javascript function* main() { @@ -1224,9 +1221,9 @@ var it = main(); it.next(); ``` -上面代码的main函数,就是通过Ajax操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield语句构成的表达式,本身是没有值的,总是等于undefined。 +上面代码的`main`函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个`yield`,它几乎与同步操作的写法完全一样。注意,`makeAjaxCall`函数中的`next`方法,必须加上`response`参数,因为`yield`表达式,本身是没有值的,总是等于`undefined`。 -下面是另一个例子,通过Generator函数逐行读取文本文件。 +下面是另一个例子,通过 Generator 函数逐行读取文本文件。 ```javascript function* numbers() { @@ -1241,7 +1238,7 @@ function* numbers() { } ``` -上面代码打开文本文件,使用yield语句可以手动逐行读取文件。 +上面代码打开文本文件,使用`yield`表达式可以手动逐行读取文件。 ### (2)控制流管理 @@ -1259,7 +1256,7 @@ step1(function (value1) { }); ``` -采用Promise改写上面的代码。 +采用 Promise 改写上面的代码。 ```javascript Promise.resolve(step1) @@ -1274,7 +1271,7 @@ Promise.resolve(step1) .done(); ``` -上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量Promise的语法。Generator函数可以进一步改善代码运行流程。 +上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量 Promise 的语法。Generator 函数可以进一步改善代码运行流程。 ```javascript function* longRunningTask(value1) { @@ -1320,22 +1317,22 @@ function *iterateSteps(steps){ } ``` -上面代码中,数组`steps`封装了一个任务的多个步骤,Generator函数`iterateSteps`则是依次为这些步骤加上`yield`命令。 +上面代码中,数组`steps`封装了一个任务的多个步骤,Generator 函数`iterateSteps`则是依次为这些步骤加上`yield`命令。 将任务分解成步骤之后,还可以将项目分解成多个依次执行的任务。 ```javascript let jobs = [job1, job2, job3]; -function *iterateJobs(jobs){ +function* iterateJobs(jobs){ for (var i=0; i< jobs.length; i++){ var job = jobs[i]; - yield *iterateSteps(job.steps); + yield* iterateSteps(job.steps); } } ``` -上面代码中,数组`jobs`封装了一个项目的多个任务,Generator函数`iterateJobs`则是依次为这些任务加上`yield *`命令。 +上面代码中,数组`jobs`封装了一个项目的多个任务,Generator 函数`iterateJobs`则是依次为这些任务加上`yield*`命令。 最后,就可以用`for...of`循环一次性依次执行所有任务的所有步骤。 @@ -1360,9 +1357,9 @@ while (!res.done){ } ``` -### (3)部署Iterator接口 +### (3)部署 Iterator 接口 -利用Generator函数,可以在任意对象上部署Iterator接口。 +利用 Generator 函数,可以在任意对象上部署 Iterator 接口。 ```javascript function* iterEntries(obj) { @@ -1383,9 +1380,9 @@ for (let [key, value] of iterEntries(myObj)) { // bar 7 ``` -上述代码中,`myObj`是一个普通对象,通过`iterEntries`函数,就有了Iterator接口。也就是说,可以在任意对象上部署`next`方法。 +上述代码中,`myObj`是一个普通对象,通过`iterEntries`函数,就有了 Iterator 接口。也就是说,可以在任意对象上部署`next`方法。 -下面是一个对数组部署Iterator接口的例子,尽管数组原生具有这个接口。 +下面是一个对数组部署 Iterator 接口的例子,尽管数组原生具有这个接口。 ```javascript function* makeSimpleGenerator(array){ @@ -1405,7 +1402,7 @@ gen.next().done // true ### (4)作为数据结构 -Generator可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为Generator函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。 +Generator 可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为 Generator 函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。 ```javascript function *doStuff() { @@ -1415,7 +1412,7 @@ function *doStuff() { } ``` -上面代码就是依次返回三个函数,但是由于使用了Generator函数,导致可以像处理数组那样,处理这三个返回的函数。 +上面代码就是依次返回三个函数,但是由于使用了 Generator 函数,导致可以像处理数组那样,处理这三个返回的函数。 ```javascript for (task of doStuff()) { @@ -1423,7 +1420,7 @@ for (task of doStuff()) { } ``` -实际上,如果用ES5表达,完全可以用数组模拟Generator的这种用法。 +实际上,如果用 ES5 表达,完全可以用数组模拟 Generator 的这种用法。 ```javascript function doStuff() { @@ -1435,5 +1432,5 @@ function doStuff() { } ``` -上面的函数,可以用一模一样的for...of循环处理!两相一比较,就不难看出Generator使得数据或者操作,具备了类似数组的接口。 +上面的函数,可以用一模一样的`for...of`循环处理!两相一比较,就不难看出 Generator 使得数据或者操作,具备了类似数组的接口。 From ab9781950823a2aad255e2bd6ed7488320f34671 Mon Sep 17 00:00:00 2001 From: Tse-Hsien Chiang Date: Thu, 11 May 2017 17:10:53 +0800 Subject: [PATCH 038/768] Fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正錯字:Funciton.prototype -> Function.prototype --- docs/class.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/class.md b/docs/class.md index 3904e0779..7a71c27a1 100644 --- a/docs/class.md +++ b/docs/class.md @@ -695,7 +695,7 @@ A.__proto__ === Function.prototype // true A.prototype.__proto__ === Object.prototype // true ``` -这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承`Funciton.prototype`。但是,`A`调用后返回一个空对象(即`Object`实例),所以`A.prototype.__proto__`指向构造函数(`Object`)的`prototype`属性。 +这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承`Function.prototype`。但是,`A`调用后返回一个空对象(即`Object`实例),所以`A.prototype.__proto__`指向构造函数(`Object`)的`prototype`属性。 第三种特殊情况,子类继承`null`。 @@ -707,7 +707,7 @@ A.__proto__ === Function.prototype // true A.prototype.__proto__ === undefined // true ``` -这种情况与第二种情况非常像。`A`也是一个普通函数,所以直接继承`Funciton.prototype`。但是,A调用后返回的对象不继承任何方法,所以它的`__proto__`指向`Function.prototype`,即实质上执行了下面的代码。 +这种情况与第二种情况非常像。`A`也是一个普通函数,所以直接继承`Function.prototype`。但是,A调用后返回的对象不继承任何方法,所以它的`__proto__`指向`Function.prototype`,即实质上执行了下面的代码。 ```javascript class C extends null { From 136b9371beb35f32f90dbf3be7b1b464fc22b46d Mon Sep 17 00:00:00 2001 From: Jacty Date: Tue, 16 May 2017 11:25:01 +0800 Subject: [PATCH 039/768] typo typo --- docs/module-loader.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index 036f9c01b..8dd7856fa 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -23,7 +23,7 @@ 默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到` @@ -274,7 +274,7 @@ export {}; 上面的命令并不是输出一个空对象,而是不输出任何接口的 ES6 标准写法。 -如何不指定绝对路径,Node 加载 ES6 模块会依次寻找以下脚本,与`require()`的规则一致。 +如果不指定绝对路径,Node 加载 ES6 模块会依次寻找以下脚本,与`require()`的规则一致。 ```javascript import './foo'; From e01c68988bb41e68a68f98b2771ea87d1e3bbc73 Mon Sep 17 00:00:00 2001 From: Jacty Date: Tue, 16 May 2017 13:25:23 +0800 Subject: [PATCH 040/768] typo typo --- docs/style.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/style.md b/docs/style.md index 37f1ae4d7..33696789b 100644 --- a/docs/style.md +++ b/docs/style.md @@ -436,7 +436,7 @@ const Breadcrumbs = React.createClass({ export default Breadcrumbs ``` -如果模块只有一个输出值,就使用`export default`,如果模块有多个输出值,就不使用`export default`,不要`export default`与普通的`export`同时使用。 +如果模块只有一个输出值,就使用`export default`,如果模块有多个输出值,就不使用`export default`,`export default`与普通的`export`不要同时使用。 不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。 From 63fba269329c21e12ed6e262cafed537cf0c752b Mon Sep 17 00:00:00 2001 From: walkthecat <58568819@qq.com> Date: Wed, 17 May 2017 10:07:02 +0800 Subject: [PATCH 041/768] Update simd.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上面代码中,`Uint16`的最小值是`0`,`subSaturate`的最小值是`-32678`。一旦运算发生溢出,就返回最小值。 修改为 上面代码中,`Uint16`的最小值是`0`,`Int16`的最小值是`-32678`。一旦运算发生溢出,就返回最小值。 --- docs/simd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/simd.md b/docs/simd.md index 65e0f64c3..5ecb388a1 100644 --- a/docs/simd.md +++ b/docs/simd.md @@ -167,7 +167,7 @@ SIMD.Int16x8.subSaturate(c, d) // Int16x8[-32768, 0, 0, 0, 0, 0, 0, 0, 0] ``` -上面代码中,`Uint16`的最小值是`0`,`subSaturate`的最小值是`-32678`。一旦运算发生溢出,就返回最小值。 +上面代码中,`Uint16`的最小值是`0`,`Int16`的最小值是`-32678`。一旦运算发生溢出,就返回最小值。 ### SIMD.%type%.mul(),SIMD.%type%.div(),SIMD.%type%.sqrt() From 6cffe77c7c3a9ce87ed9dfa1519522324adbbc21 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 19 May 2017 16:41:41 +0800 Subject: [PATCH 042/768] docs(decorator): edit decorator --- docs/decorator.md | 16 ++++++++-------- docs/set-map.md | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/decorator.md b/docs/decorator.md index 690370f38..e9cfd748b 100644 --- a/docs/decorator.md +++ b/docs/decorator.md @@ -2,19 +2,19 @@ ## 类的修饰 -修饰器(Decorator)是一个函数,用来修改类的行为。这是ES7的一个[提案](https://github.com/wycats/javascript-decorators),目前Babel转码器已经支持。 - -修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。 +修饰器(Decorator)是一个函数,用来修改类的行为。这是 ES 的一个[提案](https://github.com/wycats/javascript-decorators),目前 Babel 转码器已经支持。 ```javascript +@testable +class MyTestableClass { + // ... +} + function testable(target) { target.isTestable = true; } -@testable -class MyTestableClass {} - -console.log(MyTestableClass.isTestable) // true +MyTestableClass.isTestable // true ``` 上面代码中,`@testable`就是一个修饰器。它修改了`MyTestableClass`这个类的行为,为它加上了静态属性`isTestable`。 @@ -31,7 +31,7 @@ class A {} A = decorator(A) || A; ``` -也就是说,修饰器本质就是编译时执行的函数。 +注意,修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。 修饰器函数的第一个参数,就是所要修饰的目标类。 diff --git a/docs/set-map.md b/docs/set-map.md index 954cdbf8a..6583eed23 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -890,7 +890,7 @@ jsonToMap('[[true,7],[{"foo":3},["abc"]]]') ### 含义 -`WeakMap`结构与`Map`结构类似,也是用于生成键值对。 +`WeakMap`结构与`Map`结构类似,也是用于生成键值对的集合。 ```javascript // WeakMap 可以使用 set 方法添加成员 From 2f89b0de2d3afdaba85fc38ca764f20197cf9b61 Mon Sep 17 00:00:00 2001 From: P-ppc Date: Fri, 19 May 2017 17:53:59 +0800 Subject: [PATCH 043/768] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dpromise.md=E7=AC=A6?= =?UTF-8?q?=E5=8F=B7=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将全角单引号修正为半角单引号 --- docs/promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/promise.md b/docs/promise.md index 18d796b5d..eeed778a4 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -633,7 +633,7 @@ console.log('one'); // three ``` -上面代码中,`setTimeout(fn, 0)`在下一轮“事件循环”开始时执行,`Promise.resolve()`在本轮“事件循环”结束时执行,`console.log(’one‘)`则是立即执行,因此最先输出。 +上面代码中,`setTimeout(fn, 0)`在下一轮“事件循环”开始时执行,`Promise.resolve()`在本轮“事件循环”结束时执行,`console.log('one')`则是立即执行,因此最先输出。 ## Promise.reject() From bb7ce158b3226d97f5c7f8c095c8021691d822f6 Mon Sep 17 00:00:00 2001 From: xiangyangLi Date: Wed, 24 May 2017 16:00:40 +0800 Subject: [PATCH 044/768] =?UTF-8?q?=E7=BC=BA=E5=B0=91=E6=8B=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generator.md b/docs/generator.md index 209c8acdf..0a30b50ec 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -130,7 +130,7 @@ var flat = function* (a) { } else { yield item; } - } + }); }; for (var f of flat(arr)){ From 7acf0179717f0d8b2d353cc42aa4492c3f79cddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B1=BC=E9=A6=99=E8=8C=84=E5=AD=90?= Date: Thu, 25 May 2017 11:37:24 +0800 Subject: [PATCH 045/768] typo fixed --- docs/array.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/array.md b/docs/array.md index 33f5bd1be..ad93b13c1 100644 --- a/docs/array.md +++ b/docs/array.md @@ -357,7 +357,7 @@ if (arr.indexOf(el) !== -1) { } ``` -`indexOf`方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相当运算符(===)进行判断,这会导致对`NaN`的误判。 +`indexOf`方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对`NaN`的误判。 ```javascript [NaN].indexOf(NaN) From 495337e85bee6e94dbda12e43d77ac8522746e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B1=BC=E9=A6=99=E8=8C=84=E5=AD=90?= Date: Thu, 25 May 2017 20:44:37 +0800 Subject: [PATCH 046/768] typo fixed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将“并排的两个双冒号”修改为“并排的两个冒号” --- docs/function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/function.md b/docs/function.md index 52f11d8bd..842260b5c 100644 --- a/docs/function.md +++ b/docs/function.md @@ -1168,7 +1168,7 @@ var fix = f => (x => f(v => x(x)(v))) 箭头函数可以绑定`this`对象,大大减少了显式绑定`this`对象的写法(`call`、`apply`、`bind`)。但是,箭头函数并不适用于所有场合,所以ES7提出了“函数绑定”(function bind)运算符,用来取代`call`、`apply`、`bind`调用。虽然该语法还是ES7的一个[提案](https://github.com/zenparsing/es-function-bind),但是Babel转码器已经支持。 -函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。 +函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。 ```javascript foo::bar; From b939b7725c467f1802b6ffb1e0d89cfe65aa29b6 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 25 May 2017 20:52:05 +0800 Subject: [PATCH 047/768] docs(promise): edit promise --- docs/promise.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 18d796b5d..e8d665c0e 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -351,19 +351,19 @@ someAsyncThing().then(function() { }); ``` -上面代码中,`someAsyncThing`函数产生的Promise对象会报错,但是由于没有指定`catch`方法,这个错误不会被捕获,也不会传递到外层代码,导致运行后没有任何输出。注意,Chrome浏览器不遵守这条规定,它会抛出错误“ReferenceError: x is not defined”。 +上面代码中,`someAsyncThing`函数产生的 Promise 对象会报错,但是由于没有指定`catch`方法,这个错误不会被捕获,也不会传递到外层代码。正常情况下,运行后不会有任何输出,但是浏览器此时会打印出错误“ReferenceError: x is not defined”,不过不会终止脚本执行,如果这个脚本放在服务器执行,退出码就是`0`(即表示执行成功)。 ```javascript -var promise = new Promise(function(resolve, reject) { +var promise = new Promise(function (resolve, reject) { resolve('ok'); - setTimeout(function() { throw new Error('test') }, 0) + setTimeout(function () { throw new Error('test') }, 0) }); -promise.then(function(value) { console.log(value) }); +promise.then(function (value) { console.log(value) }); // ok // Uncaught Error: test ``` -上面代码中,Promise 指定在下一轮“事件循环”再抛出错误,结果由于没有指定使用`try...catch`语句,就冒泡到最外层,成了未捕获的错误。因为此时,Promise的函数体已经运行结束了,所以这个错误是在Promise函数体外抛出的。 +上面代码中,Promise 指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise 的运行已经结束了,所以这个错误是在 Promise 函数体外抛出的,会冒泡到最外层,成了未捕获的错误。 Node 有一个`unhandledRejection`事件,专门监听未捕获的`reject`错误。 @@ -373,7 +373,7 @@ process.on('unhandledRejection', function (err, p) { }); ``` -上面代码中,`unhandledRejection`事件的监听函数有两个参数,第一个是错误对象,第二个是报错的Promise实例,它可以用来了解发生错误的环境信息。。 +上面代码中,`unhandledRejection`事件的监听函数有两个参数,第一个是错误对象,第二个是报错的 Promise 实例,它可以用来了解发生错误的环境信息。。 需要注意的是,`catch`方法返回的还是一个 Promise 对象,因此后面还可以接着调用`then`方法。 From 23c05f824420ee99f5dfee65e801af96d320b5b4 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 26 May 2017 17:25:33 +0800 Subject: [PATCH 048/768] docs(function): edit arrow function --- docs/function.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/function.md b/docs/function.md index 52f11d8bd..d6a843e0d 100644 --- a/docs/function.md +++ b/docs/function.md @@ -225,7 +225,7 @@ foo(undefined, null) 上面代码中,`length`属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了3个参数,其中有一个参数`c`指定了默认值,因此`length`属性等于`3`减去`1`,最后得到`2`。 -这是因为`length`属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数也不会计入`length`属性。 +这是因为`length`属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest 参数也不会计入`length`属性。 ```javascript (function(...args) {}).length // 0 @@ -937,7 +937,7 @@ var result = values.sort(function (a, b) { var result = values.sort((a, b) => a - b); ``` -下面是rest参数与箭头函数结合的例子。 +下面是 rest 参数与箭头函数结合的例子。 ```javascript const numbers = (...nums) => nums; @@ -959,9 +959,9 @@ headAndTail(1, 2, 3, 4, 5) (2)不可以当作构造函数,也就是说,不可以使用`new`命令,否则会抛出一个错误。 -(3)不可以使用`arguments`对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。 +(3)不可以使用`arguments`对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 -(4)不可以使用`yield`命令,因此箭头函数不能用作Generator函数。 +(4)不可以使用`yield`命令,因此箭头函数不能用作 Generator 函数。 上面四点中,第一点尤其值得注意。`this`对象的指向是可变的,但是在箭头函数中,它是固定的。 @@ -1004,7 +1004,7 @@ setTimeout(() => console.log('s2: ', timer.s2), 3100); 上面代码中,`Timer`函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的`this`绑定定义时所在的作用域(即`Timer`函数),后者的`this`指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,`timer.s1`被更新了3次,而`timer.s2`一次都没更新。 -箭头函数可以让`this`指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM事件的回调函数封装在一个对象里面。 +箭头函数可以让`this`指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。 ```javascript var handler = { @@ -1025,7 +1025,7 @@ var handler = { `this`指向的固定化,并不是因为箭头函数内部有绑定`this`的机制,实际原因是箭头函数根本没有自己的`this`,导致内部的`this`就是外层代码块的`this`。正是因为它没有`this`,所以也就不能用作构造函数。 -所以,箭头函数转成ES5的代码如下。 +所以,箭头函数转成 ES5 的代码如下。 ```javascript // ES6 @@ -1097,11 +1097,11 @@ foo(2, 4, 6, 8) 上面代码中,箭头函数没有自己的`this`,所以`bind`方法无效,内部的`this`指向外部的`this`。 -长期以来,JavaScript语言的`this`对象一直是一个令人头痛的问题,在对象方法中使用`this`,必须非常小心。箭头函数”绑定”`this`,很大程度上解决了这个困扰。 +长期以来,JavaScript 语言的`this`对象一直是一个令人头痛的问题,在对象方法中使用`this`,必须非常小心。箭头函数”绑定”`this`,很大程度上解决了这个困扰。 ### 嵌套的箭头函数 -箭头函数内部,还可以再使用箭头函数。下面是一个ES5语法的多重嵌套函数。 +箭头函数内部,还可以再使用箭头函数。下面是一个 ES5 语法的多重嵌套函数。 ```javascript function insert(value) { From 3347915274ac6b28989dafc1fdf4b587664af679 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 28 May 2017 10:29:23 +0800 Subject: [PATCH 049/768] docs(class): edit class --- docs/class.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/class.md b/docs/class.md index 7a71c27a1..6fc4e4d8f 100644 --- a/docs/class.md +++ b/docs/class.md @@ -4,7 +4,7 @@ ### 概述 -JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子。 +在 JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。 ```javascript function Point(x, y) { @@ -19,9 +19,9 @@ Point.prototype.toString = function () { var p = new Point(1, 2); ``` -上面这种写法跟传统的面向对象语言(比如C++和Java)差异很大,很容易让新学习这门语言的程序员感到困惑。 +上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。 -ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过`class`关键字,可以定义类。基本上,ES6的`class`可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的`class`写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用ES6的“类”改写,就是下面这样。 +ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过`class`关键字,可以定义类。基本上,ES6 的`class`可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的`class`写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的`class`改写,就是下面这样。 ```javascript //定义类 From 3c1b9269502553ae78aa7ddb034046e835b5581d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B1=BC=E9=A6=99=E8=8C=84=E5=AD=90?= Date: Mon, 29 May 2017 00:08:53 +0800 Subject: [PATCH 050/768] Typo fixed --- docs/iterator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iterator.md b/docs/iterator.md index 62c388f2c..b87c1f96c 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -792,7 +792,7 @@ for (let value of myArray) { ``` - 有着同`for...in`一样的简洁语法,但是没有`for...in`那些缺点。 -- 不同用于`forEach`方法,它可以与`break`、`continue`和`return`配合使用。 +- 不同于`forEach`方法,它可以与`break`、`continue`和`return`配合使用。 - 提供了遍历所有数据结构的统一操作接口。 下面是一个使用break语句,跳出`for...of`循环的例子。 From 8a2c746996407c66155ab7b2c1457ef99b16ccd7 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 31 May 2017 20:54:06 +0800 Subject: [PATCH 051/768] docs(let): edit let --- docs/let.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/let.md b/docs/let.md index 7c42ed921..b46ae1320 100644 --- a/docs/let.md +++ b/docs/let.md @@ -57,7 +57,7 @@ a[6](); // 6 上面代码中,变量`i`是`let`声明的,当前的`i`只在本轮循环有效,所以每一次循环的`i`其实都是一个新的变量,所以最后输出的是`6`。你可能会问,如果每一轮循环的变量`i`都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量`i`时,就在上一轮循环的基础上进行计算。 -另外,`for`循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。 +另外,`for`循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。 ```javascript for (let i = 0; i < 3; i++) { @@ -69,7 +69,7 @@ for (let i = 0; i < 3; i++) { // abc ``` -上面代码输出了3次`abc`,这表明函数内部的变量`i`和外部的变量`i`是分离的。 +上面代码输出了3次`abc`,这表明函数内部的变量`i`不同于循环变量`i`,有自己单独的作用域。 ### 不存在变量提升 From d0865f07738d3c650e2e8673935ff9886b0c7935 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 4 Jun 2017 12:42:27 +0800 Subject: [PATCH 052/768] docs: edit Object --- docs/object.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/object.md b/docs/object.md index 8e0c80a1b..acf4f16ff 100644 --- a/docs/object.md +++ b/docs/object.md @@ -632,40 +632,40 @@ Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable ## 属性的遍历 -ES6一共有5种方法可以遍历对象的属性。 +ES6 一共有5种方法可以遍历对象的属性。 **(1)for...in** -`for...in`循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。 +`for...in`循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。 **(2)Object.keys(obj)** -`Object.keys`返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。 +`Object.keys`返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)。 **(3)Object.getOwnPropertyNames(obj)** -`Object.getOwnPropertyNames`返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。 +`Object.getOwnPropertyNames`返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)。 **(4)Object.getOwnPropertySymbols(obj)** -`Object.getOwnPropertySymbols`返回一个数组,包含对象自身的所有Symbol属性。 +`Object.getOwnPropertySymbols`返回一个数组,包含对象自身的所有 Symbol 属性。 **(5)Reflect.ownKeys(obj)** -`Reflect.ownKeys`返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举。 +`Reflect.ownKeys`返回一个数组,包含对象自身的所有属性,不管属性名是 Symbol 或字符串,也不管是否可枚举。 以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。 - 首先遍历所有属性名为数值的属性,按照数字排序。 - 其次遍历所有属性名为字符串的属性,按照生成时间排序。 -- 最后遍历所有属性名为Symbol值的属性,按照生成时间排序。 +- 最后遍历所有属性名为 Symbol 值的属性,按照生成时间排序。 ```javascript Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) // ['2', '10', 'b', 'a', Symbol()] ``` -上面代码中,`Reflect.ownKeys`方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性`2`和`10`,其次是字符串属性`b`和`a`,最后是Symbol属性。 +上面代码中,`Reflect.ownKeys`方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性`2`和`10`,其次是字符串属性`b`和`a`,最后是 Symbol 属性。 ## `__proto__`属性,Object.setPrototypeOf(),Object.getPrototypeOf() @@ -1337,6 +1337,10 @@ let a = {a: 'a'}; let b = {b: 'b'}; let c = {c: 'c'}; let d = mix(c).with(a, b); + +d.c // "c" +d.b // "b" +d.a // "a" ``` 上面代码中,对象`a`和`b`被混入了对象`c`。 From 4019d9d01cb1af1e7cd6b5c5b8e24c7665fbffaa Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 4 Jun 2017 12:47:03 +0800 Subject: [PATCH 053/768] docs: edit Object --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index acf4f16ff..4b490ccea 100644 --- a/docs/object.md +++ b/docs/object.md @@ -1343,7 +1343,7 @@ d.b // "b" d.a // "a" ``` -上面代码中,对象`a`和`b`被混入了对象`c`。 +上面代码返回一个新的对象`d`,代表了对象`a`和`b`被混入了对象`c`的操作。 出于完整性的考虑,`Object.getOwnPropertyDescriptors`进入标准以后,还会有`Reflect.getOwnPropertyDescriptors`方法。 From 38816c896a46b964eae773006f053785cb692aeb Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 4 Jun 2017 13:04:36 +0800 Subject: [PATCH 054/768] docs: edit Promise --- docs/promise.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 7f1db8be7..7142b2c3b 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -16,13 +16,13 @@ Promise 是异步编程的一种解决方案,比传统的解决方案——回 `Promise`也有一些缺点。首先,无法取消`Promise`,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,`Promise`内部抛出的错误,不会反应到外部。第三,当处于`Pending`状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 -如果某些事件不断地反复发生,一般来说,使用 stream 模式是比部署`Promise`更好的选择。 +如果某些事件不断地反复发生,一般来说,使用 [Stream](https://nodejs.org/api/stream.html) 模式是比部署`Promise`更好的选择。 ## 基本用法 -ES6规定,Promise对象是一个构造函数,用来生成Promise实例。 +ES6 规定,`Promise`对象是一个构造函数,用来生成`Promise`实例。 -下面代码创造了一个Promise实例。 +下面代码创造了一个`Promise`实例。 ```javascript var promise = new Promise(function(resolve, reject) { @@ -36,11 +36,11 @@ var promise = new Promise(function(resolve, reject) { }); ``` -Promise构造函数接受一个函数作为参数,该函数的两个参数分别是`resolve`和`reject`。它们是两个函数,由JavaScript引擎提供,不用自己部署。 +`Promise`构造函数接受一个函数作为参数,该函数的两个参数分别是`resolve`和`reject`。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。 -`resolve`函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;`reject`函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。 +`resolve`函数的作用是,将`Promise`对象的状态从“未完成”变为“成功”(即从 Pending 变为 Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;`reject`函数的作用是,将`Promise`对象的状态从“未完成”变为“失败”(即从 Pending 变为 Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。 -Promise实例生成以后,可以用`then`方法分别指定`Resolved`状态和`Reject`状态的回调函数。 +`Promise`实例生成以后,可以用`then`方法分别指定`Resolved`状态和`Reject`状态的回调函数。 ```javascript promise.then(function(value) { @@ -50,9 +50,9 @@ promise.then(function(value) { }); ``` -`then`方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。 +`then`方法可以接受两个回调函数作为参数。第一个回调函数是`Promise`对象的状态变为`Resolved`时调用,第二个回调函数是`Promise`对象的状态变为`Rejected`时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受`Promise`对象传出的值作为参数。 -下面是一个Promise对象的简单例子。 +下面是一个`Promise`对象的简单例子。 ```javascript function timeout(ms) { @@ -66,9 +66,9 @@ timeout(100).then((value) => { }); ``` -上面代码中,`timeout`方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(`ms`参数)以后,Promise实例的状态变为Resolved,就会触发`then`方法绑定的回调函数。 +上面代码中,`timeout`方法返回一个`Promise`实例,表示一段时间以后才会发生的结果。过了指定的时间(`ms`参数)以后,`Promise`实例的状态变为`Resolved`,就会触发`then`方法绑定的回调函数。 -Promise新建后就会立即执行。 +Promise 新建后就会立即执行。 ```javascript let promise = new Promise(function(resolve, reject) { @@ -87,7 +87,7 @@ console.log('Hi!'); // Resolved ``` -上面代码中,Promise新建后立即执行,所以首先输出的是“Promise”。然后,`then`方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以“Resolved”最后输出。 +上面代码中,Promise 新建后立即执行,所以首先输出的是`Promise`。然后,`then`方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以`Resolved`最后输出。 下面是异步加载图片的例子。 @@ -109,9 +109,9 @@ function loadImageAsync(url) { } ``` -上面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用`resolve`方法,否则就调用`reject`方法。 +上面代码中,使用`Promise`包装了一个图片加载的异步操作。如果加载成功,就调用`resolve`方法,否则就调用`reject`方法。 -下面是一个用Promise对象实现的Ajax操作的例子。 +下面是一个用`Promise`对象实现的 Ajax 操作的例子。 ```javascript var getJSON = function(url) { @@ -145,9 +145,9 @@ getJSON("/posts.json").then(function(json) { }); ``` -上面代码中,`getJSON`是对XMLHttpRequest对象的封装,用于发出一个针对JSON数据的HTTP请求,并且返回一个Promise对象。需要注意的是,在`getJSON`内部,`resolve`函数和`reject`函数调用时,都带有参数。 +上面代码中,`getJSON`是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个`Promise`对象。需要注意的是,在`getJSON`内部,`resolve`函数和`reject`函数调用时,都带有参数。 -如果调用`resolve`函数和`reject`函数时带有参数,那么它们的参数会被传递给回调函数。`reject`函数的参数通常是Error对象的实例,表示抛出的错误;`resolve`函数的参数除了正常的值以外,还可能是另一个Promise实例,表示异步操作的结果有可能是一个值,也有可能是另一个异步操作,比如像下面这样。 +如果调用`resolve`函数和`reject`函数时带有参数,那么它们的参数会被传递给回调函数。`reject`函数的参数通常是`Error`对象的实例,表示抛出的错误;`resolve`函数的参数除了正常的值以外,还可能是另一个 Promise 实例,表示异步操作的结果有可能是一个值,也有可能是另一个异步操作,比如像下面这样。 ```javascript var p1 = new Promise(function (resolve, reject) { @@ -183,9 +183,9 @@ p2 ## Promise.prototype.then() -Promise实例具有`then`方法,也就是说,`then`方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。前面说过,`then`方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。 +Promise 实例具有`then`方法,也就是说,`then`方法是定义在原型对象`Promise.prototype`上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,`then`方法的第一个参数是`Resolved`状态的回调函数,第二个参数(可选)是`Rejected`状态的回调函数。 -`then`方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即`then`方法后面再调用另一个`then`方法。 +`then`方法返回的是一个新的`Promise`实例(注意,不是原来那个`Promise`实例)。因此可以采用链式写法,即`then`方法后面再调用另一个`then`方法。 ```javascript getJSON("/posts.json").then(function(json) { @@ -197,7 +197,7 @@ getJSON("/posts.json").then(function(json) { 上面的代码使用`then`方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。 -采用链式的`then`,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。 +采用链式的`then`,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个`Promise`对象(即有异步操作),这时后一个回调函数,就会等待该`Promise`对象的状态发生变化,才会被调用。 ```javascript getJSON("/post/1.json").then(function(post) { @@ -209,7 +209,7 @@ getJSON("/post/1.json").then(function(post) { }); ``` -上面代码中,第一个`then`方法指定的回调函数,返回的是另一个Promise对象。这时,第二个`then`方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为Resolved,就调用`funcA`,如果状态变为Rejected,就调用`funcB`。 +上面代码中,第一个`then`方法指定的回调函数,返回的是另一个`Promise`对象。这时,第二个`then`方法指定的回调函数,就会等待这个新的`Promise`对象状态发生变化。如果变为`Resolved`,就调用`funcA`,如果状态变为`Rejected`,就调用`funcB`。 如果采用箭头函数,上面的代码可以写得更简洁。 From 3e302652a0124115a827b3040a1f5ad34c135a7f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 4 Jun 2017 18:59:41 +0800 Subject: [PATCH 055/768] docs(Promise): edit Promise.all --- docs/promise.md | 50 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 7142b2c3b..6b9106ba2 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -453,13 +453,13 @@ someAsyncThing().then(function() { ## Promise.all() -`Promise.all`方法用于将多个Promise实例,包装成一个新的Promise实例。 +`Promise.all`方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 ```javascript var p = Promise.all([p1, p2, p3]); ``` -上面代码中,`Promise.all`方法接受一个数组作为参数,`p1`、`p2`、`p3`都是Promise对象的实例,如果不是,就会先调用下面讲到的`Promise.resolve`方法,将参数转为Promise实例,再进一步处理。(`Promise.all`方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。) +上面代码中,`Promise.all`方法接受一个数组作为参数,`p1`、`p2`、`p3`都是 Promise 实例,如果不是,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。(`Promise.all`方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。) `p`的状态由`p1`、`p2`、`p3`决定,分成两种情况。 @@ -472,7 +472,7 @@ var p = Promise.all([p1, p2, p3]); ```javascript // 生成一个Promise对象的数组 var promises = [2, 3, 5, 7, 11, 13].map(function (id) { - return getJSON("/post/" + id + ".json"); + return getJSON('/post/' + id + ".json"); }); Promise.all(promises).then(function (posts) { @@ -482,7 +482,7 @@ Promise.all(promises).then(function (posts) { }); ``` -上面代码中,`promises`是包含6个Promise实例的数组,只有这6个实例的状态都变成`fulfilled`,或者其中有一个变为`rejected`,才会调用`Promise.all`方法后面的回调函数。 +上面代码中,`promises`是包含6个 Promise 实例的数组,只有这6个实例的状态都变成`fulfilled`,或者其中有一个变为`rejected`,才会调用`Promise.all`方法后面的回调函数。 下面是另一个例子。 @@ -504,6 +504,48 @@ Promise.all([ 上面代码中,`booksPromise`和`userPromise`是两个异步操作,只有等到它们的结果都返回了,才会触发`pickTopRecommentations`这个回调函数。 +注意,如果作为参数的 Promise 实例,自己定义了`catch`方法,那么它一旦被`rejected`,并不会触发`Promise.all()`的`catch`方法。 + +```javascript +const p1 = new Promise((resolve, reject) => { + resolve('hello'); +}) +.then(result => result) +.catch(e => e); + +const p2 = new Promise((resolve, reject) => { + throw new Error('报错了'); +}) +.then(result => result) +.catch(e => e); + +Promise.all([p1, p2]) +.then(result => console.log(result)) +.catch(e => console.log(e)); +// ["hello", Error: 报错了] +``` + +上面代码中,`p1`会`resolved`,`p2`首先会`rejected`,但是`p2`有自己的`catch`方法,该方法返回的是一个新的 Promise 实例,`p2`指向的实际上是这个实例。该实例执行完`catch`方法后,也会变成`resolved`,导致`Promise.all()`方法参数里面的两个实例都会`resolved`,因此会调用`then`方法指定的回调函数,而不会调用`catch`方法指定的回调函数。 + +如果`p2`没有自己的`catch`方法,就会调用`Promise.all()`的`catch`方法。 + +```javascript +const p1 = new Promise((resolve, reject) => { + resolve('hello'); +}) +.then(result => result); + +const p2 = new Promise((resolve, reject) => { + throw new Error('报错了'); +}) +.then(result => result); + +Promise.all([p1, p2]) +.then(result => console.log(result)) +.catch(e => console.log(e)); +// Error: 报错了 +``` + ## Promise.race() `Promise.race`方法同样是将多个Promise实例,包装成一个新的Promise实例。 From 9a16eba5790bc9f18abe500b7e3f5e2ca263de70 Mon Sep 17 00:00:00 2001 From: Cody Chan Date: Tue, 6 Jun 2017 00:34:50 +0800 Subject: [PATCH 056/768] Fix typo --- docs/module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module.md b/docs/module.md index 009592804..dca0ede99 100644 --- a/docs/module.md +++ b/docs/module.md @@ -171,7 +171,7 @@ setTimeout(() => foo = 'baz', 500); 上面代码输出变量`foo`,值为`bar`,500毫秒之后变成`baz`。 -这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新,详见下文《ES6模块加载的实质》一节。 +这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新,详见下文《Module 的加载实现》一节。 最后,`export`命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的`import`命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了ES6模块的设计初衷。 From 165ba46cea6ac1bd70101e543675e00a62ee557c Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 7 Jun 2017 09:22:06 +0800 Subject: [PATCH 057/768] docs: add class-extends --- docs/class-extends.md | 682 +++++++++++++++++++++++++ docs/class.md | 1114 +++++++++-------------------------------- docs/let.md | 18 +- sidebar.md | 3 +- 4 files changed, 924 insertions(+), 893 deletions(-) create mode 100644 docs/class-extends.md diff --git a/docs/class-extends.md b/docs/class-extends.md new file mode 100644 index 000000000..89e3bec82 --- /dev/null +++ b/docs/class-extends.md @@ -0,0 +1,682 @@ +# Class 的继承 + +## 简介 + +Class 可以通过`extends`关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。 + +```javascript +class Point { +} + +class ColorPoint extends Point { +} +``` + +上面代码定义了一个`ColorPoint`类,该类通过`extends`关键字,继承了`Point`类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个`Point`类。下面,我们在`ColorPoint`内部加上代码。 + +```javascript +class ColorPoint extends Point { + constructor(x, y, color) { + super(x, y); // 调用父类的constructor(x, y) + this.color = color; + } + + toString() { + return this.color + ' ' + super.toString(); // 调用父类的toString() + } +} +``` + +上面代码中,`constructor`方法和`toString`方法之中,都出现了`super`关键字,它在这里表示父类的构造函数,用来新建父类的`this`对象。 + +子类必须在`constructor`方法中调用`super`方法,否则新建实例时会报错。这是因为子类没有自己的`this`对象,而是继承父类的`this`对象,然后对其进行加工。如果不调用`super`方法,子类就得不到`this`对象。 + +```javascript +class Point { /* ... */ } + +class ColorPoint extends Point { + constructor() { + } +} + +let cp = new ColorPoint(); // ReferenceError +``` + +上面代码中,`ColorPoint`继承了父类`Point`,但是它的构造函数没有调用`super`方法,导致新建实例时报错。 + +ES5 的继承,实质是先创造子类的实例对象`this`,然后再将父类的方法添加到`this`上面(`Parent.apply(this)`)。ES6 的继承机制完全不同,实质是先创造父类的实例对象`this`(所以必须先调用`super`方法),然后再用子类的构造函数修改`this`。 + +如果子类没有定义`constructor`方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有`constructor`方法。 + +```javascript +class ColorPoint extends Point { +} + +// 等同于 +class ColorPoint extends Point { + constructor(...args) { + super(...args); + } +} +``` + +另一个需要注意的地方是,在子类的构造函数中,只有调用`super`之后,才可以使用`this`关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有`super`方法才能返回父类实例。 + +```javascript +class Point { + constructor(x, y) { + this.x = x; + this.y = y; + } +} + +class ColorPoint extends Point { + constructor(x, y, color) { + this.color = color; // ReferenceError + super(x, y); + this.color = color; // 正确 + } +} +``` + +上面代码中,子类的`constructor`方法没有调用`super`之前,就使用`this`关键字,结果报错,而放在`super`方法之后就是正确的。 + +下面是生成子类实例的代码。 + +```javascript +let cp = new ColorPoint(25, 8, 'green'); + +cp instanceof ColorPoint // true +cp instanceof Point // true +``` + +上面代码中,实例对象`cp`同时是`ColorPoint`和`Point`两个类的实例,这与 ES5 的行为完全一致。 + +## Object.getPrototypeOf() + +`Object.getPrototypeOf`方法可以用来从子类上获取父类。 + +```javascript +Object.getPrototypeOf(ColorPoint) === Point +// true +``` + +因此,可以使用这个方法判断,一个类是否继承了另一个类。 + +## super 关键字 + +`super`这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。 + +第一种情况,`super`作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次`super`函数。 + +```javascript +class A {} + +class B extends A { + constructor() { + super(); + } +} +``` + +上面代码中,子类`B`的构造函数之中的`super()`,代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。 + +注意,`super`虽然代表了父类`A`的构造函数,但是返回的是子类`B`的实例,即`super`内部的`this`指的是`B`,因此`super()`在这里相当于`A.prototype.constructor.call(this)`。 + +```javascript +class A { + constructor() { + console.log(new.target.name); + } +} +class B extends A { + constructor() { + super(); + } +} +new A() // A +new B() // B +``` + +上面代码中,`new.target`指向当前正在执行的函数。可以看到,在`super()`执行时,它指向的是子类`B`的构造函数,而不是父类`A`的构造函数。也就是说,`super()`内部的`this`指向的是`B`。 + +作为函数时,`super()`只能用在子类的构造函数之中,用在其他地方就会报错。 + +```javascript +class A {} + +class B extends A { + m() { + super(); // 报错 + } +} +``` + +上面代码中,`super()`用在`B`类的`m`方法之中,就会造成句法错误。 + +第二种情况,`super`作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。 + +```javascript +class A { + p() { + return 2; + } +} + +class B extends A { + constructor() { + super(); + console.log(super.p()); // 2 + } +} + +let b = new B(); +``` + +上面代码中,子类`B`当中的`super.p()`,就是将`super`当作一个对象使用。这时,`super`在普通方法之中,指向`A.prototype`,所以`super.p()`就相当于`A.prototype.p()`。 + +这里需要注意,由于`super`指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过`super`调用的。 + +```javascript +class A { + constructor() { + this.p = 2; + } +} + +class B extends A { + get m() { + return super.p; + } +} + +let b = new B(); +b.m // undefined +``` + +上面代码中,`p`是父类`A`实例的属性,`super.p`就引用不到它。 + +如果属性定义在父类的原型对象上,`super`就可以取到。 + +```javascript +class A {} +A.prototype.x = 2; + +class B extends A { + constructor() { + super(); + console.log(super.x) // 2 + } +} + +let b = new B(); +``` + +上面代码中,属性`x`是定义在`A.prototype`上面的,所以`super.x`可以取到它的值。 + +ES6 规定,通过`super`调用父类的方法时,`super`会绑定子类的`this`。 + +```javascript +class A { + constructor() { + this.x = 1; + } + print() { + console.log(this.x); + } +} + +class B extends A { + constructor() { + super(); + this.x = 2; + } + m() { + super.print(); + } +} + +let b = new B(); +b.m() // 2 +``` + +上面代码中,`super.print()`虽然调用的是`A.prototype.print()`,但是`A.prototype.print()`会绑定子类`B`的`this`,导致输出的是`2`,而不是`1`。也就是说,实际上执行的是`super.print.call(this)`。 + +由于绑定子类的`this`,所以如果通过`super`对某个属性赋值,这时`super`就是`this`,赋值的属性会变成子类实例的属性。 + +```javascript +class A { + constructor() { + this.x = 1; + } +} + +class B extends A { + constructor() { + super(); + this.x = 2; + super.x = 3; + console.log(super.x); // undefined + console.log(this.x); // 3 + } +} + +let b = new B(); +``` + +上面代码中,`super.x`赋值为`3`,这时等同于对`this.x`赋值为`3`。而当读取`super.x`的时候,读的是`A.prototype.x`,所以返回`undefined`。 + +如果`super`作为对象,用在静态方法之中,这时`super`将指向父类,而不是父类的原型对象。 + +```javascript +class Parent { + static myMethod(msg) { + console.log('static', msg); + } + + myMethod(msg) { + console.log('instance', msg); + } +} + +class Child extends Parent { + static myMethod(msg) { + super.myMethod(msg); + } + + myMethod(msg) { + super.myMethod(msg); + } +} + +Child.myMethod(1); // static 1 + +var child = new Child(); +child.myMethod(2); // instance 2 +``` + +上面代码中,`super`在静态方法之中指向父类,在普通方法之中指向父类的原型对象。 + +注意,使用`super`的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。 + +```javascript +class A {} + +class B extends A { + constructor() { + super(); + console.log(super); // 报错 + } +} +``` + +上面代码中,`console.log(super)`当中的`super`,无法看出是作为函数使用,还是作为对象使用,所以 JavaScript 引擎解析代码的时候就会报错。这时,如果能清晰地表明`super`的数据类型,就不会报错。 + +```javascript +class A {} + +class B extends A { + constructor() { + super(); + console.log(super.valueOf() instanceof B); // true + } +} + +let b = new B(); +``` + +上面代码中,`super.valueOf()`表明`super`是一个对象,因此就不会报错。同时,由于`super`绑定`B`的`this`,所以`super.valueOf()`返回的是一个`B`的实例。 + +最后,由于对象总是继承其他对象的,所以可以在任意一个对象中,使用`super`关键字。 + +```javascript +var obj = { + toString() { + return "MyObject: " + super.toString(); + } +}; + +obj.toString(); // MyObject: [object Object] +``` + +## 类的 prototype 属性和\_\_proto\_\_属性 + +大多数浏览器的 ES5 实现之中,每一个对象都有`__proto__`属性,指向对应的构造函数的`prototype`属性。Class 作为构造函数的语法糖,同时有`prototype`属性和`__proto__`属性,因此同时存在两条继承链。 + +(1)子类的`__proto__`属性,表示构造函数的继承,总是指向父类。 + +(2)子类`prototype`属性的`__proto__`属性,表示方法的继承,总是指向父类的`prototype`属性。 + +```javascript +class A { +} + +class B extends A { +} + +B.__proto__ === A // true +B.prototype.__proto__ === A.prototype // true +``` + +上面代码中,子类`B`的`__proto__`属性指向父类`A`,子类`B`的`prototype`属性的`__proto__`属性指向父类`A`的`prototype`属性。 + +这样的结果是因为,类的继承是按照下面的模式实现的。 + +```javascript +class A { +} + +class B { +} + +// B 的实例继承 A 的实例 +Object.setPrototypeOf(B.prototype, A.prototype); + +// B 的实例继承 A 的静态属性 +Object.setPrototypeOf(B, A); + +const b = new B(); +``` + +《对象的扩展》一章给出过`Object.setPrototypeOf`方法的实现。 + +```javascript +Object.setPrototypeOf = function (obj, proto) { + obj.__proto__ = proto; + return obj; +} +``` + +因此,就得到了上面的结果。 + +```javascript +Object.setPrototypeOf(B.prototype, A.prototype); +// 等同于 +B.prototype.__proto__ = A.prototype; + +Object.setPrototypeOf(B, A); +// 等同于 +B.__proto__ = A; +``` + +这两条继承链,可以这样理解:作为一个对象,子类(`B`)的原型(`__proto__`属性)是父类(`A`);作为一个构造函数,子类(`B`)的原型(`prototype`属性)是父类的实例。 + +```javascript +Object.create(A.prototype); +// 等同于 +B.prototype.__proto__ = A.prototype; +``` + +### extends 的继承目标 + +`extends`关键字后面可以跟多种类型的值。 + +```javascript +class B extends A { +} +``` + +上面代码的`A`,只要是一个有`prototype`属性的函数,就能被`B`继承。由于函数都有`prototype`属性(除了`Function.prototype`函数),因此`A`可以是任意函数。 + +下面,讨论三种特殊情况。 + +第一种特殊情况,子类继承`Object`类。 + +```javascript +class A extends Object { +} + +A.__proto__ === Object // true +A.prototype.__proto__ === Object.prototype // true +``` + +这种情况下,`A`其实就是构造函数`Object`的复制,`A`的实例就是`Object`的实例。 + +第二种特殊情况,不存在任何继承。 + +```javascript +class A { +} + +A.__proto__ === Function.prototype // true +A.prototype.__proto__ === Object.prototype // true +``` + +这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承`Function.prototype`。但是,`A`调用后返回一个空对象(即`Object`实例),所以`A.prototype.__proto__`指向构造函数(`Object`)的`prototype`属性。 + +第三种特殊情况,子类继承`null`。 + +```javascript +class A extends null { +} + +A.__proto__ === Function.prototype // true +A.prototype.__proto__ === undefined // true +``` + +这种情况与第二种情况非常像。`A`也是一个普通函数,所以直接继承`Function.prototype`。但是,A调用后返回的对象不继承任何方法,所以它的`__proto__`指向`Function.prototype`,即实质上执行了下面的代码。 + +```javascript +class C extends null { + constructor() { return Object.create(null); } +} +``` + +### 实例的 \_\_proto\_\_ 属性 + +子类实例的`__proto__`属性的`__proto__`属性,指向父类实例的`__proto__`属性。也就是说,子类的原型的原型,是父类的原型。 + +```javascript +var p1 = new Point(2, 3); +var p2 = new ColorPoint(2, 3, 'red'); + +p2.__proto__ === p1.__proto__ // false +p2.__proto__.__proto__ === p1.__proto__ // true +``` + +上面代码中,`ColorPoint`继承了`Point`,导致前者原型的原型是后者的原型。 + +因此,通过子类实例的`__proto__.__proto__`属性,可以修改父类实例的行为。 + +```javascript +p2.__proto__.__proto__.printName = function () { + console.log('Ha'); +}; + +p1.printName() // "Ha" +``` + +上面代码在`ColorPoint`的实例`p2`上向`Point`类添加方法,结果影响到了`Point`的实例`p1`。 + +## 原生构造函数的继承 + +原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript 的原生构造函数大致有下面这些。 + +- Boolean() +- Number() +- String() +- Array() +- Date() +- Function() +- RegExp() +- Error() +- Object() + +以前,这些原生构造函数是无法继承的,比如,不能自己定义一个`Array`的子类。 + +```javascript +function MyArray() { + Array.apply(this, arguments); +} + +MyArray.prototype = Object.create(Array.prototype, { + constructor: { + value: MyArray, + writable: true, + configurable: true, + enumerable: true + } +}); +``` + +上面代码定义了一个继承Array的`MyArray`类。但是,这个类的行为与`Array`完全不一致。 + +```javascript +var colors = new MyArray(); +colors[0] = "red"; +colors.length // 0 + +colors.length = 0; +colors[0] // "red" +``` + +之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过`Array.apply()`或者分配给原型对象都不行。原生构造函数会忽略`apply`方法传入的`this`,也就是说,原生构造函数的`this`无法绑定,导致拿不到内部属性。 + +ES5 是先新建子类的实例对象`this`,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。比如,`Array`构造函数有一个内部属性`[[DefineOwnProperty]]`,用来定义新属性时,更新`length`属性,这个内部属性无法在子类获取,导致子类的`length`属性行为不正常。 + +下面的例子中,我们想让一个普通对象继承`Error`对象。 + +```javascript +var e = {}; + +Object.getOwnPropertyNames(Error.call(e)) +// [ 'stack' ] + +Object.getOwnPropertyNames(e) +// [] +``` + +上面代码中,我们想通过`Error.call(e)`这种写法,让普通对象`e`具有`Error`对象的实例属性。但是,`Error.call()`完全忽略传入的第一个参数,而是返回一个新对象,`e`本身没有任何变化。这证明了`Error.call(e)`这种写法,无法继承原生构造函数。 + +ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象`this`,然后再用子类的构造函数修饰`this`,使得父类的所有行为都可以继承。下面是一个继承`Array`的例子。 + +```javascript +class MyArray extends Array { + constructor(...args) { + super(...args); + } +} + +var arr = new MyArray(); +arr[0] = 12; +arr.length // 1 + +arr.length = 0; +arr[0] // undefined +``` + +上面代码定义了一个`MyArray`类,继承了`Array`构造函数,因此就可以从`MyArray`生成数组的实例。这意味着,ES6 可以自定义原生数据结构(比如`Array`、`String`等)的子类,这是 ES5 无法做到的。 + +上面这个例子也说明,`extends`关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构。下面就是定义了一个带版本功能的数组。 + +```javascript +class VersionedArray extends Array { + constructor() { + super(); + this.history = [[]]; + } + commit() { + this.history.push(this.slice()); + } + revert() { + this.splice(0, this.length, ...this.history[this.history.length - 1]); + } +} + +var x = new VersionedArray(); + +x.push(1); +x.push(2); +x // [1, 2] +x.history // [[]] + +x.commit(); +x.history // [[], [1, 2]] +x.push(3); +x // [1, 2, 3] + +x.revert(); +x // [1, 2] +``` + +上面代码中,`VersionedArray`结构会通过`commit`方法,将自己的当前状态存入`history`属性,然后通过`revert`方法,可以撤销当前版本,回到上一个版本。除此之外,`VersionedArray`依然是一个数组,所有原生的数组方法都可以在它上面调用。 + +下面是一个自定义`Error`子类的例子。 + +```javascript +class ExtendableError extends Error { + constructor(message) { + super(); + this.message = message; + this.stack = (new Error()).stack; + this.name = this.constructor.name; + } +} + +class MyError extends ExtendableError { + constructor(m) { + super(m); + } +} + +var myerror = new MyError('ll'); +myerror.message // "ll" +myerror instanceof Error // true +myerror.name // "MyError" +myerror.stack +// Error +// at MyError.ExtendableError +// ... +``` + +注意,继承`Object`的子类,有一个[行为差异](http://stackoverflow.com/questions/36203614/super-does-not-pass-arguments-when-instantiating-a-class-extended-from-object)。 + +```javascript +class NewObj extends Object{ + constructor(){ + super(...arguments); + } +} +var o = new NewObj({attr: true}); +console.log(o.attr === true); // false +``` + +上面代码中,`NewObj`继承了`Object`,但是无法通过`super`方法向父类`Object`传参。这是因为ES6改变了`Object`构造函数的行为,一旦发现`Object`方法不是通过`new Object()`这种形式调用,ES6 规定`Object`构造函数会忽略参数。 + +## Mixin 模式的实现 + +Mixin 模式指的是,将多个类的接口“混入”(mix in)另一个类。它在 ES6 的实现如下。 + +```javascript +function mix(...mixins) { + class Mix {} + + for (let mixin of mixins) { + copyProperties(Mix, mixin); + copyProperties(Mix.prototype, mixin.prototype); + } + + return Mix; +} + +function copyProperties(target, source) { + for (let key of Reflect.ownKeys(source)) { + if ( key !== "constructor" + && key !== "prototype" + && key !== "name" + ) { + let desc = Object.getOwnPropertyDescriptor(source, key); + Object.defineProperty(target, key, desc); + } + } +} +``` + +上面代码的`mix`函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可。 + +```javascript +class DistributedEdit extends mix(Loggable, Serializable) { + // ... +} +``` + diff --git a/docs/class.md b/docs/class.md index 6fc4e4d8f..a3ce57670 100644 --- a/docs/class.md +++ b/docs/class.md @@ -1,10 +1,8 @@ -# Class +# Class 的基本语法 -## Class基本语法 +## 简介 -### 概述 - -在 JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。 +JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。 ```javascript function Point(x, y) { @@ -21,7 +19,9 @@ var p = new Point(1, 2); 上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。 -ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过`class`关键字,可以定义类。基本上,ES6 的`class`可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的`class`写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的`class`改写,就是下面这样。 +ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过`class`关键字,可以定义类。 + +基本上,ES6 的`class`可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的`class`写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的`class`改写,就是下面这样。 ```javascript //定义类 @@ -37,11 +37,11 @@ class Point { } ``` -上面代码定义了一个“类”,可以看到里面有一个`constructor`方法,这就是构造方法,而`this`关键字则代表实例对象。也就是说,ES5的构造函数`Point`,对应ES6的`Point`类的构造方法。 +上面代码定义了一个“类”,可以看到里面有一个`constructor`方法,这就是构造方法,而`this`关键字则代表实例对象。也就是说,ES5 的构造函数`Point`,对应 ES6 的`Point`类的构造方法。 `Point`类除了构造方法,还定义了一个`toString`方法。注意,定义“类”的方法的时候,前面不需要加上`function`这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。 -ES6的类,完全可以看作构造函数的另一种写法。 +ES6 的类,完全可以看作构造函数的另一种写法。 ```javascript class Point { @@ -67,19 +67,19 @@ var b = new Bar(); b.doStuff() // "stuff" ``` -构造函数的`prototype`属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的`prototype`属性上面。 +构造函数的`prototype`属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的`prototype`属性上面。 ```javascript class Point { - constructor(){ + constructor() { // ... } - toString(){ + toString() { // ... } - toValue(){ + toValue() { // ... } } @@ -87,8 +87,9 @@ class Point { // 等同于 Point.prototype = { - toString(){}, - toValue(){} + constructor() {}, + toString() {}, + toValue() {}, }; ``` @@ -101,7 +102,7 @@ let b = new B(); b.constructor === B.prototype.constructor // true ``` -上面代码中,`b`是B类的实例,它的`constructor`方法就是B类原型的`constructor`方法。 +上面代码中,`b`是`B`类的实例,它的`constructor`方法就是`B`类原型的`constructor`方法。 由于类的方法都定义在`prototype`对象上面,所以类的新方法可以添加在`prototype`对象上面。`Object.assign`方法可以很方便地一次向类添加多个方法。 @@ -118,7 +119,7 @@ Object.assign(Point.prototype, { }); ``` -`prototype`对象的`constructor`属性,直接指向“类”的本身,这与ES5的行为是一致的。 +`prototype`对象的`constructor`属性,直接指向“类”的本身,这与 ES5 的行为是一致的。 ```javascript Point.prototype.constructor === Point // true @@ -143,7 +144,7 @@ Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"] ``` -上面代码中,`toString`方法是`Point`类内部定义的方法,它是不可枚举的。这一点与ES5的行为不一致。 +上面代码中,`toString`方法是`Point`类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。 ```javascript var Point = function (x, y) { @@ -160,13 +161,14 @@ Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"] ``` -上面代码采用ES5的写法,`toString`方法就是可枚举的。 +上面代码采用 ES5 的写法,`toString`方法就是可枚举的。 类的属性名,可以采用表达式。 ```javascript -let methodName = "getArea"; -class Square{ +let methodName = 'getArea'; + +class Square { constructor(length) { // ... } @@ -179,14 +181,28 @@ class Square{ 上面代码中,`Square`类的方法名`getArea`,是从表达式得到的。 -### constructor方法 +## 严格模式 + +类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。 + +考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。 + +## constructor 方法 `constructor`方法是类的默认方法,通过`new`命令生成对象实例时,自动调用该方法。一个类必须有`constructor`方法,如果没有显式定义,一个空的`constructor`方法会被默认添加。 ```javascript -constructor() {} +class Point { +} + +// 等同于 +class Point { + constructor() {} +} ``` +上面代码中,定义了一个空的类`Point`,JavaScript 引擎会自动为它添加一个空的`constructor`方法。 + `constructor`方法默认返回实例对象(即`this`),完全可以指定返回另外一个对象。 ```javascript @@ -202,7 +218,7 @@ new Foo() instanceof Foo 上面代码中,`constructor`函数返回一个全新的对象,结果导致实例对象不是`Foo`类的实例。 -类的构造函数,不使用`new`是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用`new`也可以执行。 +类必须使用`new`调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用`new`也可以执行。 ```javascript class Foo { @@ -215,11 +231,15 @@ Foo() // TypeError: Class constructor Foo cannot be invoked without 'new' ``` -### 类的实例对象 +## 类的实例对象 -生成类的实例对象的写法,与ES5完全一样,也是使用`new`命令。如果忘记加上`new`,像函数那样调用`Class`,将会报错。 +生成类的实例对象的写法,与 ES5 完全一样,也是使用`new`命令。前面说过,如果忘记加上`new`,像函数那样调用`Class`,将会报错。 ```javascript +class Point { + // ... +} + // 报错 var point = Point(2, 3); @@ -227,7 +247,7 @@ var point = Point(2, 3); var point = new Point(2, 3); ``` -与ES5一样,实例的属性除非显式定义在其本身(即定义在`this`对象上),否则都是定义在原型上(即定义在`class`上)。 +与 ES5 一样,实例的属性除非显式定义在其本身(即定义在`this`对象上),否则都是定义在原型上(即定义在`class`上)。 ```javascript //定义类 @@ -254,9 +274,9 @@ point.hasOwnProperty('toString') // false point.__proto__.hasOwnProperty('toString') // true ``` -上面代码中,`x`和`y`都是实例对象`point`自身的属性(因为定义在`this`变量上),所以`hasOwnProperty`方法返回`true`,而`toString`是原型对象的属性(因为定义在`Point`类上),所以`hasOwnProperty`方法返回`false`。这些都与ES5的行为保持一致。 +上面代码中,`x`和`y`都是实例对象`point`自身的属性(因为定义在`this`变量上),所以`hasOwnProperty`方法返回`true`,而`toString`是原型对象的属性(因为定义在`Point`类上),所以`hasOwnProperty`方法返回`false`。这些都与 ES5 的行为保持一致。 -与ES5一样,类的所有实例共享一个原型对象。 +与 ES5 一样,类的所有实例共享一个原型对象。 ```javascript var p1 = new Point(2,3); @@ -266,9 +286,9 @@ p1.__proto__ === p2.__proto__ //true ``` -上面代码中,`p1`和`p2`都是Point的实例,它们的原型都是Point.prototype,所以`__proto__`属性是相等的。 +上面代码中,`p1`和`p2`都是`Point`的实例,它们的原型都是`Point.prototype`,所以`__proto__`属性是相等的。 -这也意味着,可以通过实例的`__proto__`属性为Class添加方法。 +这也意味着,可以通过实例的`__proto__`属性为“类”添加方法。 ```javascript var p1 = new Point(2,3); @@ -283,30 +303,9 @@ var p3 = new Point(4,2); p3.printName() // "Oops" ``` -上面代码在`p1`的原型上添加了一个`printName`方法,由于`p1`的原型就是`p2`的原型,因此`p2`也可以调用这个方法。而且,此后新建的实例`p3`也可以调用这个方法。这意味着,使用实例的`__proto__`属性改写原型,必须相当谨慎,不推荐使用,因为这会改变Class的原始定义,影响到所有实例。 - -### 不存在变量提升 - -Class不存在变量提升(hoist),这一点与ES5完全不同。 - -```javascript -new Foo(); // ReferenceError -class Foo {} -``` - -上面代码中,`Foo`类使用在前,定义在后,这样会报错,因为ES6不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。 - -```javascript -{ - let Foo = class {}; - class Bar extends Foo { - } -} -``` - -上面的代码不会报错,因为`Bar`继承`Foo`的时候,`Foo`已经有定义了。但是,如果存在`class`的提升,上面代码就会报错,因为`class`会被提升到代码头部,而`let`命令是不提升的,所以导致`Bar`继承`Foo`的时候,`Foo`还没有定义。 +上面代码在`p1`的原型上添加了一个`printName`方法,由于`p1`的原型就是`p2`的原型,因此`p2`也可以调用这个方法。而且,此后新建的实例`p3`也可以调用这个方法。这意味着,使用实例的`__proto__`属性改写原型,必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。 -### Class表达式 +## Class 表达式 与函数一样,类也可以使用表达式的形式定义。 @@ -318,7 +317,7 @@ const MyClass = class Me { }; ``` -上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是`MyClass`而不是`Me`,`Me`只在Class的内部代码可用,指代当前类。 +上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是`MyClass`而不是`Me`,`Me`只在 Class 的内部代码可用,指代当前类。 ```javascript let inst = new MyClass(); @@ -326,7 +325,7 @@ inst.getClassName() // Me Me.name // ReferenceError: Me is not defined ``` -上面代码表示,`Me`只在Class内部有定义。 +上面代码表示,`Me`只在 Class 内部有定义。 如果类的内部没用到的话,可以省略`Me`,也就是可以写成下面的形式。 @@ -334,7 +333,7 @@ Me.name // ReferenceError: Me is not defined const MyClass = class { /* ... */ }; ``` -采用Class表达式,可以写出立即执行的Class。 +采用 Class 表达式,可以写出立即执行的 Class。 ```javascript let person = new class { @@ -352,7 +351,28 @@ person.sayName(); // "张三" 上面代码中,`person`是一个立即执行的类的实例。 -### 私有方法 +## 不存在变量提升 + +类不存在变量提升(hoist),这一点与 ES5 完全不同。 + +```javascript +new Foo(); // ReferenceError +class Foo {} +``` + +上面代码中,`Foo`类使用在前,定义在后,这样会报错,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。 + +```javascript +{ + let Foo = class {}; + class Bar extends Foo { + } +} +``` + +上面的代码不会报错,因为`Bar`继承`Foo`的时候,`Foo`已经有定义了。但是,如果存在`class`的提升,上面代码就会报错,因为`class`会被提升到代码头部,而`let`命令是不提升的,所以导致`Bar`继承`Foo`的时候,`Foo`还没有定义。 + +## 私有方法 私有方法是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。 @@ -419,7 +439,51 @@ export default class myClass{ 上面代码中,`bar`和`snaf`都是`Symbol`值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。 -### this的指向 +## 私有属性 + +与私有方法一样,ES6 不支持私有属性。目前,有一个[提案](https://github.com/tc39/proposal-private-fields),为`class`加了私有属性。方法是在属性名之前,使用`#`表示。 + +```javascript +class Point { + #x; + + constructor(x = 0) { + #x = +x; + } + + get x() { return #x } + set x(value) { #x = +value } +} +``` + +上面代码中,`#x`就表示私有属性`x`,在`Point`类之外是读取不到这个属性的。还可以看到,私有属性与实例的属性是可以同名的(比如,`#x`与`get x()`)。 + +私有属性可以指定初始值,在构造函数执行时进行初始化。 + +```javascript +class Point { + #x = 0; + constructor() { + #x; // 0 + } +} +``` + +之所以要引入一个新的前缀`#`表示私有属性,而没有采用`private`关键字,是因为 JavaScript 是一门动态语言,使用独立的符号似乎是唯一的可靠方法,能够准确地区分一种属性是否为私有属性。另外,Ruby 语言使用`@`表示私有属性,ES6 没有用这个符号而使用`#`,是因为`@`已经被留给了 Decorator。 + +该提案只规定了私有属性的写法。但是,很自然地,它也可以用来写私有方法。 + +```javascript +class Foo { + #a; + #b; + #sum() { return #a + #b; } + printSum() { console.log(#sum()); } + constructor(a, b) { #a = a; #b = b; } +} +``` + +## this 的指向 类的方法内部如果含有`this`,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。 @@ -491,15 +555,9 @@ function selfish (target) { const logger = selfish(new Logger()); ``` -### 严格模式 - -类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。 - -考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。 - -### name属性 +## name 属性 -由于本质上,ES6的类只是ES5的构造函数的一层包装,所以函数的许多特性都被`Class`继承,包括`name`属性。 +由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被`Class`继承,包括`name`属性。 ```javascript class Point {} @@ -508,824 +566,191 @@ Point.name // "Point" `name`属性总是返回紧跟在`class`关键字后面的类名。 -## Class的继承 +## Class 的取值函数(getter)和存值函数(setter) -### 基本用法 - -Class之间可以通过`extends`关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。 - -```javascript -class ColorPoint extends Point {} -``` - -上面代码定义了一个`ColorPoint`类,该类通过`extends`关键字,继承了`Point`类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个`Point`类。下面,我们在`ColorPoint`内部加上代码。 +与 ES5 一样,在“类”的内部可以使用`get`和`set`关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。 ```javascript -class ColorPoint extends Point { - constructor(x, y, color) { - super(x, y); // 调用父类的constructor(x, y) - this.color = color; +class MyClass { + constructor() { + // ... } - - toString() { - return this.color + ' ' + super.toString(); // 调用父类的toString() + get prop() { + return 'getter'; + } + set prop(value) { + console.log('setter: '+value); } } -``` -上面代码中,`constructor`方法和`toString`方法之中,都出现了`super`关键字,它在这里表示父类的构造函数,用来新建父类的`this`对象。 - -子类必须在`constructor`方法中调用`super`方法,否则新建实例时会报错。这是因为子类没有自己的`this`对象,而是继承父类的`this`对象,然后对其进行加工。如果不调用`super`方法,子类就得不到`this`对象。 - -```javascript -class Point { /* ... */ } +let inst = new MyClass(); -class ColorPoint extends Point { - constructor() { - } -} +inst.prop = 123; +// setter: 123 -let cp = new ColorPoint(); // ReferenceError +inst.prop +// 'getter' ``` -上面代码中,`ColorPoint`继承了父类`Point`,但是它的构造函数没有调用`super`方法,导致新建实例时报错。 - -ES5的继承,实质是先创造子类的实例对象`this`,然后再将父类的方法添加到`this`上面(`Parent.apply(this)`)。ES6的继承机制完全不同,实质是先创造父类的实例对象`this`(所以必须先调用`super`方法),然后再用子类的构造函数修改`this`。 +上面代码中,`prop`属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。 -如果子类没有定义`constructor`方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有`constructor`方法。 +存值函数和取值函数是设置在属性的 Descriptor 对象上的。 ```javascript -constructor(...args) { - super(...args); -} -``` - -另一个需要注意的地方是,在子类的构造函数中,只有调用`super`之后,才可以使用`this`关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有`super`方法才能返回父类实例。 +class CustomHTMLElement { + constructor(element) { + this.element = element; + } -```javascript -class Point { - constructor(x, y) { - this.x = x; - this.y = y; + get html() { + return this.element.innerHTML; } -} -class ColorPoint extends Point { - constructor(x, y, color) { - this.color = color; // ReferenceError - super(x, y); - this.color = color; // 正确 + set html(value) { + this.element.innerHTML = value; } } -``` - -上面代码中,子类的`constructor`方法没有调用`super`之前,就使用`this`关键字,结果报错,而放在`super`方法之后就是正确的。 -下面是生成子类实例的代码。 - -```javascript -let cp = new ColorPoint(25, 8, 'green'); +var descriptor = Object.getOwnPropertyDescriptor( + CustomHTMLElement.prototype, "html" +); -cp instanceof ColorPoint // true -cp instanceof Point // true +"get" in descriptor // true +"set" in descriptor // true ``` -上面代码中,实例对象`cp`同时是`ColorPoint`和`Point`两个类的实例,这与ES5的行为完全一致。 - -### 类的prototype属性和\_\_proto\_\_属性 +上面代码中,存值函数和取值函数是定义在`html`属性的描述对象上面,这与 ES5 完全一致。 -大多数浏览器的ES5实现之中,每一个对象都有`__proto__`属性,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同时有prototype属性和`__proto__`属性,因此同时存在两条继承链。 - -(1)子类的`__proto__`属性,表示构造函数的继承,总是指向父类。 +## Class 的 Generator 方法 -(2)子类`prototype`属性的`__proto__`属性,表示方法的继承,总是指向父类的`prototype`属性。 +如果某个方法之前加上星号(`*`),就表示该方法是一个 Generator 函数。 ```javascript -class A { +class Foo { + constructor(...args) { + this.args = args; + } + * [Symbol.iterator]() { + for (let arg of this.args) { + yield arg; + } + } } -class B extends A { +for (let x of new Foo('hello', 'world')) { + console.log(x); } - -B.__proto__ === A // true -B.prototype.__proto__ === A.prototype // true +// hello +// world ``` -上面代码中,子类`B`的`__proto__`属性指向父类`A`,子类`B`的`prototype`属性的`__proto__`属性指向父类`A`的`prototype`属性。 +上面代码中,`Foo`类的`Symbol.iterator`方法前有一个星号,表示该方法是一个 Generator 函数。`Symbol.iterator`方法返回一个`Foo`类的默认遍历器,`for...of`循环会自动调用这个遍历器。 + +## Class 的静态方法 -这样的结果是因为,类的继承是按照下面的模式实现的。 +类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上`static`关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。 ```javascript -class A { -} - -class B { +class Foo { + static classMethod() { + return 'hello'; + } } -// B的实例继承A的实例 -Object.setPrototypeOf(B.prototype, A.prototype); -const b = new B(); +Foo.classMethod() // 'hello' -// B的实例继承A的静态属性 -Object.setPrototypeOf(B, A); -const b = new B(); +var foo = new Foo(); +foo.classMethod() +// TypeError: foo.classMethod is not a function ``` -《对象的扩展》一章给出过`Object.setPrototypeOf`方法的实现。 - -```javascript -Object.setPrototypeOf = function (obj, proto) { - obj.__proto__ = proto; - return obj; -} -``` +上面代码中,`Foo`类的`classMethod`方法前有`static`关键字,表明该方法是一个静态方法,可以直接在`Foo`类上调用(`Foo.classMethod()`),而不是在`Foo`类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。 -因此,就得到了上面的结果。 +父类的静态方法,可以被子类继承。 ```javascript -Object.setPrototypeOf(B.prototype, A.prototype); -// 等同于 -B.prototype.__proto__ = A.prototype; - -Object.setPrototypeOf(B, A); -// 等同于 -B.__proto__ = A; -``` +class Foo { + static classMethod() { + return 'hello'; + } +} -这两条继承链,可以这样理解:作为一个对象,子类(`B`)的原型(`__proto__`属性)是父类(`A`);作为一个构造函数,子类(`B`)的原型(`prototype`属性)是父类的实例。 +class Bar extends Foo { +} -```javascript -Object.create(A.prototype); -// 等同于 -B.prototype.__proto__ = A.prototype; +Bar.classMethod() // 'hello' ``` -### Extends 的继承目标 +上面代码中,父类`Foo`有一个静态方法,子类`Bar`可以调用这个方法。 -`extends`关键字后面可以跟多种类型的值。 +静态方法也是可以从`super`对象上调用的。 ```javascript -class B extends A { +class Foo { + static classMethod() { + return 'hello'; + } } -``` - -上面代码的`A`,只要是一个有`prototype`属性的函数,就能被`B`继承。由于函数都有`prototype`属性(除了`Function.prototype`函数),因此`A`可以是任意函数。 -下面,讨论三种特殊情况。 - -第一种特殊情况,子类继承Object类。 - -```javascript -class A extends Object { +class Bar extends Foo { + static classMethod() { + return super.classMethod() + ', too'; + } } -A.__proto__ === Object // true -A.prototype.__proto__ === Object.prototype // true +Bar.classMethod() // "hello, too" ``` -这种情况下,`A`其实就是构造函数`Object`的复制,`A`的实例就是`Object`的实例。 +## Class 的静态属性和实例属性 -第二种特殊情况,不存在任何继承。 +静态属性指的是 Class 本身的属性,即`Class.propName`,而不是定义在实例对象(`this`)上的属性。 ```javascript -class A { +class Foo { } -A.__proto__ === Function.prototype // true -A.prototype.__proto__ === Object.prototype // true +Foo.prop = 1; +Foo.prop // 1 ``` -这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承`Function.prototype`。但是,`A`调用后返回一个空对象(即`Object`实例),所以`A.prototype.__proto__`指向构造函数(`Object`)的`prototype`属性。 +上面的写法为`Foo`类定义了一个静态属性`prop`。 -第三种特殊情况,子类继承`null`。 +目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。 ```javascript -class A extends null { -} - -A.__proto__ === Function.prototype // true -A.prototype.__proto__ === undefined // true -``` - -这种情况与第二种情况非常像。`A`也是一个普通函数,所以直接继承`Function.prototype`。但是,A调用后返回的对象不继承任何方法,所以它的`__proto__`指向`Function.prototype`,即实质上执行了下面的代码。 +// 以下两种写法都无效 +class Foo { + // 写法一 + prop: 2 -```javascript -class C extends null { - constructor() { return Object.create(null); } + // 写法二 + static prop: 2 } -``` - -### Object.getPrototypeOf() -`Object.getPrototypeOf`方法可以用来从子类上获取父类。 - -```javascript -Object.getPrototypeOf(ColorPoint) === Point -// true +Foo.prop // undefined ``` -因此,可以使用这个方法判断,一个类是否继承了另一个类。 +ES7 有一个静态属性的[提案](https://github.com/jeffmo/es-class-properties),目前 Babel 转码器支持。 -### super 关键字 +这个提案对实例属性和静态属性,都规定了新的写法。 -`super`这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。 +(1)类的实例属性 -第一种情况,`super`作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次`super`函数。 +类的实例属性可以用等式,写入类的定义之中。 ```javascript -class A {} +class MyClass { + myProp = 42; -class B extends A { constructor() { - super(); + console.log(this.myProp); // 42 } } ``` -上面代码中,子类`B`的构造函数之中的`super()`,代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。 +上面代码中,`myProp`就是`MyClass`的实例属性。在`MyClass`的实例上,可以读取这个属性。 -注意,`super`虽然代表了父类`A`的构造函数,但是返回的是子类`B`的实例,即`super`内部的`this`指的是`B`,因此`super()`在这里相当于`A.prototype.constructor.call(this)`。 - -```javascript -class A { - constructor() { - console.log(new.target.name); - } -} -class B extends A { - constructor() { - super(); - } -} -new A() // A -new B() // B -``` - -上面代码中,`new.target`指向当前正在执行的函数。可以看到,在`super()`执行时,它指向的是子类`B`的构造函数,而不是父类`A`的构造函数。也就是说,`super()`内部的`this`指向的是`B`。 - -作为函数时,`super()`只能用在子类的构造函数之中,用在其他地方就会报错。 - -```javascript -class A {} - -class B extends A { - m() { - super(); // 报错 - } -} -``` - -上面代码中,`super()`用在`B`类的`m`方法之中,就会造成句法错误。 - -第二种情况,`super`作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。 - -```javascript -class A { - p() { - return 2; - } -} - -class B extends A { - constructor() { - super(); - console.log(super.p()); // 2 - } -} - -let b = new B(); -``` - -上面代码中,子类`B`当中的`super.p()`,就是将`super`当作一个对象使用。这时,`super`在普通方法之中,指向`A.prototype`,所以`super.p()`就相当于`A.prototype.p()`。 - -这里需要注意,由于`super`指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过`super`调用的。 - -```javascript -class A { - constructor() { - this.p = 2; - } -} - -class B extends A { - get m() { - return super.p; - } -} - -let b = new B(); -b.m // undefined -``` - -上面代码中,`p`是父类`A`实例的属性,`super.p`就引用不到它。 - -如果属性定义在父类的原型对象上,`super`就可以取到。 - -```javascript -class A {} -A.prototype.x = 2; - -class B extends A { - constructor() { - super(); - console.log(super.x) // 2 - } -} - -let b = new B(); -``` - -上面代码中,属性`x`是定义在`A.prototype`上面的,所以`super.x`可以取到它的值。 - -ES6 规定,通过`super`调用父类的方法时,`super`会绑定子类的`this`。 - -```javascript -class A { - constructor() { - this.x = 1; - } - print() { - console.log(this.x); - } -} - -class B extends A { - constructor() { - super(); - this.x = 2; - } - m() { - super.print(); - } -} - -let b = new B(); -b.m() // 2 -``` - -上面代码中,`super.print()`虽然调用的是`A.prototype.print()`,但是`A.prototype.print()`会绑定子类`B`的`this`,导致输出的是`2`,而不是`1`。也就是说,实际上执行的是`super.print.call(this)`。 - -由于绑定子类的`this`,所以如果通过`super`对某个属性赋值,这时`super`就是`this`,赋值的属性会变成子类实例的属性。 - -```javascript -class A { - constructor() { - this.x = 1; - } -} - -class B extends A { - constructor() { - super(); - this.x = 2; - super.x = 3; - console.log(super.x); // undefined - console.log(this.x); // 3 - } -} - -let b = new B(); -``` - -上面代码中,`super.x`赋值为`3`,这时等同于对`this.x`赋值为`3`。而当读取`super.x`的时候,读的是`A.prototype.x`,所以返回`undefined`。 - -如果`super`作为对象,用在静态方法之中,这时`super`将指向父类,而不是父类的原型对象。 - -```javascript -class Parent { - static myMethod(msg) { - console.log('static', msg); - } - - myMethod(msg) { - console.log('instance', msg); - } -} - -class Child extends Parent { - static myMethod(msg) { - super.myMethod(msg); - } - - myMethod(msg) { - super.myMethod(msg); - } -} - -Child.myMethod(1); // static 1 - -var child = new Child(); -child.myMethod(2); // instance 2 -``` - -上面代码中,`super`在静态方法之中指向父类,在普通方法之中指向父类的原型对象。 - -注意,使用`super`的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。 - -```javascript -class A {} - -class B extends A { - constructor() { - super(); - console.log(super); // 报错 - } -} -``` - -上面代码中,`console.log(super)`当中的`super`,无法看出是作为函数使用,还是作为对象使用,所以 JavaScript 引擎解析代码的时候就会报错。这时,如果能清晰地表明`super`的数据类型,就不会报错。 - -```javascript -class A {} - -class B extends A { - constructor() { - super(); - console.log(super.valueOf() instanceof B); // true - } -} - -let b = new B(); -``` - -上面代码中,`super.valueOf()`表明`super`是一个对象,因此就不会报错。同时,由于`super`绑定`B`的`this`,所以`super.valueOf()`返回的是一个`B`的实例。 - -最后,由于对象总是继承其他对象的,所以可以在任意一个对象中,使用`super`关键字。 - -```javascript -var obj = { - toString() { - return "MyObject: " + super.toString(); - } -}; - -obj.toString(); // MyObject: [object Object] -``` - -### 实例的\_\_proto\_\_属性 - -子类实例的\_\_proto\_\_属性的\_\_proto\_\_属性,指向父类实例的\_\_proto\_\_属性。也就是说,子类的原型的原型,是父类的原型。 - -```javascript -var p1 = new Point(2, 3); -var p2 = new ColorPoint(2, 3, 'red'); - -p2.__proto__ === p1.__proto__ // false -p2.__proto__.__proto__ === p1.__proto__ // true -``` - -上面代码中,`ColorPoint`继承了`Point`,导致前者原型的原型是后者的原型。 - -因此,通过子类实例的`__proto__.__proto__`属性,可以修改父类实例的行为。 - -```javascript -p2.__proto__.__proto__.printName = function () { - console.log('Ha'); -}; - -p1.printName() // "Ha" -``` - -上面代码在`ColorPoint`的实例`p2`上向`Point`类添加方法,结果影响到了`Point`的实例`p1`。 - -## 原生构造函数的继承 - -原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript的原生构造函数大致有下面这些。 - -- Boolean() -- Number() -- String() -- Array() -- Date() -- Function() -- RegExp() -- Error() -- Object() - -以前,这些原生构造函数是无法继承的,比如,不能自己定义一个`Array`的子类。 - -```javascript -function MyArray() { - Array.apply(this, arguments); -} - -MyArray.prototype = Object.create(Array.prototype, { - constructor: { - value: MyArray, - writable: true, - configurable: true, - enumerable: true - } -}); -``` - -上面代码定义了一个继承Array的`MyArray`类。但是,这个类的行为与`Array`完全不一致。 - -```javascript -var colors = new MyArray(); -colors[0] = "red"; -colors.length // 0 - -colors.length = 0; -colors[0] // "red" -``` - -之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过`Array.apply()`或者分配给原型对象都不行。原生构造函数会忽略`apply`方法传入的`this`,也就是说,原生构造函数的`this`无法绑定,导致拿不到内部属性。 - -ES5是先新建子类的实例对象`this`,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。比如,Array构造函数有一个内部属性`[[DefineOwnProperty]]`,用来定义新属性时,更新`length`属性,这个内部属性无法在子类获取,导致子类的`length`属性行为不正常。 - -下面的例子中,我们想让一个普通对象继承`Error`对象。 - -```javascript -var e = {}; - -Object.getOwnPropertyNames(Error.call(e)) -// [ 'stack' ] - -Object.getOwnPropertyNames(e) -// [] -``` - -上面代码中,我们想通过`Error.call(e)`这种写法,让普通对象`e`具有`Error`对象的实例属性。但是,`Error.call()`完全忽略传入的第一个参数,而是返回一个新对象,`e`本身没有任何变化。这证明了`Error.call(e)`这种写法,无法继承原生构造函数。 - -ES6允许继承原生构造函数定义子类,因为ES6是先新建父类的实例对象`this`,然后再用子类的构造函数修饰`this`,使得父类的所有行为都可以继承。下面是一个继承`Array`的例子。 - -```javascript -class MyArray extends Array { - constructor(...args) { - super(...args); - } -} - -var arr = new MyArray(); -arr[0] = 12; -arr.length // 1 - -arr.length = 0; -arr[0] // undefined -``` - -上面代码定义了一个`MyArray`类,继承了`Array`构造函数,因此就可以从`MyArray`生成数组的实例。这意味着,ES6可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。 - -上面这个例子也说明,`extends`关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构。下面就是定义了一个带版本功能的数组。 - -```javascript -class VersionedArray extends Array { - constructor() { - super(); - this.history = [[]]; - } - commit() { - this.history.push(this.slice()); - } - revert() { - this.splice(0, this.length, ...this.history[this.history.length - 1]); - } -} - -var x = new VersionedArray(); - -x.push(1); -x.push(2); -x // [1, 2] -x.history // [[]] - -x.commit(); -x.history // [[], [1, 2]] -x.push(3); -x // [1, 2, 3] - -x.revert(); -x // [1, 2] -``` - -上面代码中,`VersionedArray`结构会通过`commit`方法,将自己的当前状态存入`history`属性,然后通过`revert`方法,可以撤销当前版本,回到上一个版本。除此之外,`VersionedArray`依然是一个数组,所有原生的数组方法都可以在它上面调用。 - -下面是一个自定义`Error`子类的例子。 - -```javascript -class ExtendableError extends Error { - constructor(message) { - super(); - this.message = message; - this.stack = (new Error()).stack; - this.name = this.constructor.name; - } -} - -class MyError extends ExtendableError { - constructor(m) { - super(m); - } -} - -var myerror = new MyError('ll'); -myerror.message // "ll" -myerror instanceof Error // true -myerror.name // "MyError" -myerror.stack -// Error -// at MyError.ExtendableError -// ... -``` - -注意,继承`Object`的子类,有一个[行为差异](http://stackoverflow.com/questions/36203614/super-does-not-pass-arguments-when-instantiating-a-class-extended-from-object)。 - -```javascript -class NewObj extends Object{ - constructor(){ - super(...arguments); - } -} -var o = new NewObj({attr: true}); -console.log(o.attr === true); // false -``` - -上面代码中,`NewObj`继承了`Object`,但是无法通过`super`方法向父类`Object`传参。这是因为ES6改变了`Object`构造函数的行为,一旦发现`Object`方法不是通过`new Object()`这种形式调用,ES6规定`Object`构造函数会忽略参数。 - -## Class的取值函数(getter)和存值函数(setter) - -与ES5一样,在Class内部可以使用`get`和`set`关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。 - -```javascript -class MyClass { - constructor() { - // ... - } - get prop() { - return 'getter'; - } - set prop(value) { - console.log('setter: '+value); - } -} - -let inst = new MyClass(); - -inst.prop = 123; -// setter: 123 - -inst.prop -// 'getter' -``` - -上面代码中,`prop`属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。 - -存值函数和取值函数是设置在属性的descriptor对象上的。 - -```javascript -class CustomHTMLElement { - constructor(element) { - this.element = element; - } - - get html() { - return this.element.innerHTML; - } - - set html(value) { - this.element.innerHTML = value; - } -} - -var descriptor = Object.getOwnPropertyDescriptor( - CustomHTMLElement.prototype, "html"); -"get" in descriptor // true -"set" in descriptor // true -``` - -上面代码中,存值函数和取值函数是定义在`html`属性的描述对象上面,这与ES5完全一致。 - -## Class 的 Generator 方法 - -如果某个方法之前加上星号(`*`),就表示该方法是一个 Generator 函数。 - -```javascript -class Foo { - constructor(...args) { - this.args = args; - } - * [Symbol.iterator]() { - for (let arg of this.args) { - yield arg; - } - } -} - -for (let x of new Foo('hello', 'world')) { - console.log(x); -} -// hello -// world -``` - -上面代码中,`Foo`类的`Symbol.iterator`方法前有一个星号,表示该方法是一个 Generator 函数。`Symbol.iterator`方法返回一个`Foo`类的默认遍历器,`for...of`循环会自动调用这个遍历器。 - -## Class 的静态方法 - -类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上`static`关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。 - -```javascript -class Foo { - static classMethod() { - return 'hello'; - } -} - -Foo.classMethod() // 'hello' - -var foo = new Foo(); -foo.classMethod() -// TypeError: foo.classMethod is not a function -``` - -上面代码中,`Foo`类的`classMethod`方法前有`static`关键字,表明该方法是一个静态方法,可以直接在`Foo`类上调用(`Foo.classMethod()`),而不是在`Foo`类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。 - -父类的静态方法,可以被子类继承。 - -```javascript -class Foo { - static classMethod() { - return 'hello'; - } -} - -class Bar extends Foo { -} - -Bar.classMethod(); // 'hello' -``` - -上面代码中,父类`Foo`有一个静态方法,子类`Bar`可以调用这个方法。 - -静态方法也是可以从`super`对象上调用的。 - -```javascript -class Foo { - static classMethod() { - return 'hello'; - } -} - -class Bar extends Foo { - static classMethod() { - return super.classMethod() + ', too'; - } -} - -Bar.classMethod(); -``` - -## Class的静态属性和实例属性 - -静态属性指的是Class本身的属性,即`Class.propname`,而不是定义在实例对象(`this`)上的属性。 - -```javascript -class Foo { -} - -Foo.prop = 1; -Foo.prop // 1 -``` - -上面的写法为`Foo`类定义了一个静态属性`prop`。 - -目前,只有这种写法可行,因为ES6明确规定,Class内部只有静态方法,没有静态属性。 - -```javascript -// 以下两种写法都无效 -class Foo { - // 写法一 - prop: 2 - - // 写法二 - static prop: 2 -} - -Foo.prop // undefined -``` - -ES7有一个静态属性的[提案](https://github.com/jeffmo/es-class-properties),目前Babel转码器支持。 - -这个提案对实例属性和静态属性,都规定了新的写法。 - -(1)类的实例属性 - -类的实例属性可以用等式,写入类的定义之中。 - -```javascript -class MyClass { - myProp = 42; - - constructor() { - console.log(this.myProp); // 42 - } -} -``` - -上面代码中,`myProp`就是`MyClass`的实例属性。在`MyClass`的实例上,可以读取这个属性。 - -以前,我们定义实例属性,只能写在类的`constructor`方法里面。 +以前,我们定义实例属性,只能写在类的`constructor`方法里面。 ```javascript class ReactCounter extends React.Component { @@ -1385,6 +810,7 @@ class MyClass { ```javascript // 老写法 class Foo { + // ... } Foo.prop = 1; @@ -1396,53 +822,9 @@ class Foo { 上面代码中,老写法的静态属性定义在类的外部。整个类生成以后,再生成静态属性。这样让人很容易忽略这个静态属性,也不符合相关代码应该放在一起的代码组织原则。另外,新写法是显式声明(declarative),而不是赋值处理,语义更好。 -## 类的私有属性 - -目前,有一个[提案](https://github.com/tc39/proposal-private-fields),为`class`加了私有属性。方法是在属性名之前,使用`#`表示。 - -```javascript -class Point { - #x; - - constructor(x = 0) { - #x = +x; - } - - get x() { return #x } - set x(value) { #x = +value } -} -``` - -上面代码中,`#x`就表示私有属性`x`,在`Point`类之外是读取不到这个属性的。还可以看到,私有属性与实例的属性是可以同名的(比如,`#x`与`get x()`)。 - -私有属性可以指定初始值,在构造函数执行时进行初始化。 - -```javascript -class Point { - #x = 0; - constructor() { - #x; // 0 - } -} -``` - -之所以要引入一个新的前缀`#`表示私有属性,而没有采用`private`关键字,是因为 JavaScript 是一门动态语言,使用独立的符号似乎是唯一的可靠方法,能够准确地区分一种属性是私有属性。另外,Ruby 语言使用`@`表示私有属性,ES6 没有用这个符号而使用`#`,是因为`@`已经被留给了 Decorator。 - -该提案只规定了私有属性的写法。但是,很自然地,它也可以用来写私有方法。 - -```javascript -class Foo { - #a; - #b; - #sum() { return #a + #b; } - printSum() { console.log(#sum()); } - constructor(a, b) { #a = a; #b = b; } -} -``` - ## new.target属性 -`new`是从构造函数生成实例的命令。ES6为`new`命令引入了一个`new.target`属性,(在构造函数中)返回`new`命令作用于的那个构造函数。如果构造函数不是通过`new`命令调用的,`new.target`会返回`undefined`,因此这个属性可以用来确定构造函数是怎么调用的。 +`new`是从构造函数生成实例的命令。ES6 为`new`命令引入了一个`new.target`属性,该属性一般用在在构造函数之中,返回`new`命令作用于的那个构造函数。如果构造函数不是通过`new`命令调用的,`new.target`会返回`undefined`,因此这个属性可以用来确定构造函数是怎么调用的。 ```javascript function Person(name) { @@ -1458,7 +840,7 @@ function Person(name) { if (new.target === Person) { this.name = name; } else { - throw new Error('必须使用new生成实例'); + throw new Error('必须使用 new 生成实例'); } } @@ -1468,7 +850,7 @@ var notAPerson = Person.call(person, '张三'); // 报错 上面代码确保构造函数只能通过`new`命令调用。 -Class内部调用`new.target`,返回当前Class。 +Class 内部调用`new.target`,返回当前 Class。 ```javascript class Rectangle { @@ -1529,39 +911,3 @@ var y = new Rectangle(3, 4); // 正确 注意,在函数外部,使用`new.target`会报错。 -## Mixin模式的实现 - -Mixin模式指的是,将多个类的接口“混入”(mix in)另一个类。它在ES6的实现如下。 - -```javascript -function mix(...mixins) { - class Mix {} - - for (let mixin of mixins) { - copyProperties(Mix, mixin); - copyProperties(Mix.prototype, mixin.prototype); - } - - return Mix; -} - -function copyProperties(target, source) { - for (let key of Reflect.ownKeys(source)) { - if ( key !== "constructor" - && key !== "prototype" - && key !== "name" - ) { - let desc = Object.getOwnPropertyDescriptor(source, key); - Object.defineProperty(target, key, desc); - } - } -} -``` - -上面代码的`mix`函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可。 - -```javascript -class DistributedEdit extends mix(Loggable, Serializable) { - // ... -} -``` diff --git a/docs/let.md b/docs/let.md index b46ae1320..abea7befb 100644 --- a/docs/let.md +++ b/docs/let.md @@ -1,10 +1,10 @@ -# let和const命令 +# let 和 const 命令 -## let命令 +## let 命令 ### 基本用法 -ES6新增了`let`命令,用来声明变量。它的用法类似于`var`,但是所声明的变量,只在`let`命令所在的代码块内有效。 +ES6 新增了`let`命令,用来声明变量。它的用法类似于`var`,但是所声明的变量,只在`let`命令所在的代码块内有效。 ```javascript { @@ -21,10 +21,12 @@ b // 1 `for`循环的计数器,就很合适使用`let`命令。 ```javascript -for (let i = 0; i < 10; i++) {} +for (let i = 0; i < 10; i++) { + // ... +} console.log(i); -//ReferenceError: i is not defined +// ReferenceError: i is not defined ``` 上面代码中,计数器`i`只在`for`循环体内有效,在循环体外引用就会报错。 @@ -41,9 +43,9 @@ for (var i = 0; i < 10; i++) { a[6](); // 10 ``` -上面代码中,变量`i`是`var`声明的,在全局范围内都有效,所以全局只有一个变量`i`。每一次循环,变量`i`的值都会发生改变,而循环内被赋给数组`a`的`function`在运行时,会通过闭包读到这同一个变量`i`,导致最后输出的是最后一轮的`i`的值,也就是10。 +上面代码中,变量`i`是`var`命令声明的,在全局范围内都有效,所以全局只有一个变量`i`。每一次循环,变量`i`的值都会发生改变,而循环内被赋给数组`a`的函数内部的`console.log(i)`,里面的`i`指向的就是全局的`i`。也就是说,所有数组`a`的成员里面的`i`,指向的都是同一个`i`,导致运行时输出的是最后一轮的`i`的值,也就是10。 -而如果使用`let`,声明的变量仅在块级作用域内有效,最后输出的是6。 +如果使用`let`,声明的变量仅在块级作用域内有效,最后输出的是6。 ```javascript var a = []; @@ -69,7 +71,7 @@ for (let i = 0; i < 3; i++) { // abc ``` -上面代码输出了3次`abc`,这表明函数内部的变量`i`不同于循环变量`i`,有自己单独的作用域。 +上面代码正确运行,输出了3次`abc`。这表明函数内部的变量`i`与循环变量`i`不在同一个作用域,有各自单独的作用域。 ### 不存在变量提升 diff --git a/sidebar.md b/sidebar.md index 4017f308d..507a3bd14 100644 --- a/sidebar.md +++ b/sidebar.md @@ -24,7 +24,8 @@ 1. [Generator 函数的语法](#docs/generator) 1. [Generator 函数的异步应用](#docs/generator-async) 1. [async 函数](#docs/async) -1. [Class](#docs/class) +1. [Class 的基本语法](#docs/class) +1. [Class 的继承](#docs/class-extends) 1. [Decorator](#docs/decorator) 1. [Module 的语法](#docs/module) 1. [Module 的加载实现](#docs/module-loader) From d3aaf30d5c29524f0c0128acb6ba905651d989d3 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 7 Jun 2017 16:47:37 +0800 Subject: [PATCH 058/768] docs: edit Intro --- docs/intro.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/intro.md b/docs/intro.md index fe7a71826..3cfc541d5 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -316,13 +316,13 @@ var es5Code = require('babel-core') // '"use strict";\n\nvar x = function x(n) {\n return n + 1;\n};' ``` -上面代码中,`transform`方法的第一个参数是一个字符串,表示需要被转换的ES6代码,第二个参数是转换的配置对象。 +上面代码中,`transform`方法的第一个参数是一个字符串,表示需要被转换的 ES6 代码,第二个参数是转换的配置对象。 ### babel-polyfill Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如`Iterator`、`Generator`、`Set`、`Maps`、`Proxy`、`Reflect`、`Symbol`、`Promise`等全局对象,以及一些定义在全局对象上的方法(比如`Object.assign`)都不会转码。 -举例来说,ES6在`Array`对象上新增了`Array.from`方法。Babel 就不会转码这个方法。如果想让这个方法运行,必须使用`babel-polyfill`,为当前环境提供一个垫片。 +举例来说,ES6 在`Array`对象上新增了`Array.from`方法。Babel 就不会转码这个方法。如果想让这个方法运行,必须使用`babel-polyfill`,为当前环境提供一个垫片。 安装命令如下。 @@ -366,7 +366,7 @@ $ browserify script.js -o bundle.js \ -t [ babelify --presets [ latest ] ] ``` -上面代码将ES6脚本`script.js`,转为`bundle.js`,浏览器直接加载后者就可以了。 +上面代码将 ES6 脚本`script.js`,转为`bundle.js`,浏览器直接加载后者就可以了。 在`package.json`设置下面的代码,就不用每次命令行都输入参数了。 @@ -430,7 +430,7 @@ Mocha 则是一个测试框架,如果需要执行使用 ES6 语法的测试脚 ## Traceur 转码器 -Google公司的[Traceur](https://github.com/google/traceur-compiler)转码器,也可以将 ES6 代码转为 ES5 代码。 +Google 公司的[Traceur](https://github.com/google/traceur-compiler)转码器,也可以将 ES6 代码转为 ES5 代码。 ### 直接插入网页 @@ -445,7 +445,7 @@ Traceur 允许将 ES6 代码直接插入网页。首先,必须在网页头部 ``` -上面代码中,一共有4个`script`标签。第一个是加载 Traceur 的库文件,第二个和第三个是将这个库文件用于浏览器环境,第四个则是加载用户脚本,这个脚本里面可以使用ES6代码。 +上面代码中,一共有4个`script`标签。第一个是加载 Traceur 的库文件,第二个和第三个是将这个库文件用于浏览器环境,第四个则是加载用户脚本,这个脚本里面可以使用 ES6 代码。 注意,第四个`script`标签的`type`属性的值是`module`,而不是`text/javascript`。这是 Traceur 编译器识别 ES6 代码的标志,编译器会自动将所有`type=module`的代码编译为 ES5,然后再交给浏览器执行。 @@ -499,7 +499,7 @@ Traceur 允许将 ES6 代码直接插入网页。首先,必须在网页头部 ### 在线转换 -Traceur也提供一个[在线编译器](http://google.github.io/traceur-compiler/demo/repl.html),可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。 +Traceur 也提供一个[在线编译器](http://google.github.io/traceur-compiler/demo/repl.html),可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。 上面的例子转为 ES5 代码运行,就是下面这个样子。 @@ -562,13 +562,13 @@ $ traceur --script calc.es6.js --out calc.es5.js --experimental ### Node 环境的用法 -Traceur 的 Node用法如下(假定已安装`traceur`模块)。 +Traceur 的 Node 用法如下(假定已安装`traceur`模块)。 ```javascript var traceur = require('traceur'); var fs = require('fs'); -// 将ES6脚本转为字符串 +// 将 ES6 脚本转为字符串 var contents = fs.readFileSync('es6-file.js').toString(); var result = traceur.compile(contents, { @@ -581,9 +581,9 @@ var result = traceur.compile(contents, { if (result.error) throw result.error; -// result对象的js属性就是转换后的ES5代码 +// result 对象的 js 属性就是转换后的 ES5 代码 fs.writeFileSync('out.js', result.js); -// sourceMap属性对应map文件 +// sourceMap 属性对应 map 文件 fs.writeFileSync('out.js.map', result.sourceMap); ``` From 6b56bb10bb248858edd28c7a454b749aefe6ff6e Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 7 Jun 2017 17:32:28 +0800 Subject: [PATCH 059/768] docs: edit decorator --- docs/decorator.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/decorator.md b/docs/decorator.md index e9cfd748b..812f094e0 100644 --- a/docs/decorator.md +++ b/docs/decorator.md @@ -2,7 +2,7 @@ ## 类的修饰 -修饰器(Decorator)是一个函数,用来修改类的行为。这是 ES 的一个[提案](https://github.com/wycats/javascript-decorators),目前 Babel 转码器已经支持。 +修饰器(Decorator)是一个函数,用来修改类的行为。ES2017 引入了这项功能,目前 Babel 转码器已经支持。 ```javascript @testable @@ -118,6 +118,23 @@ let obj = new MyClass(); obj.foo() // 'foo' ``` +实际开发中,React 与 Redux 库结合使用时,常常需要写成下面这样。 + +```javascript +class MyReactComponent extends React.Component {} + +export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent); +``` + +有了装饰器,就可以改写上面的代码。 + +```javascript +@connect(mapStateToProps, mapDispatchToProps) +export default class MyReactComponent extends React.Component {} +``` + +相对来说,后一种写法看上去更容易理解。 + ## 方法的修饰 修饰器不仅可以修饰类,还可以修饰类的属性。 @@ -289,6 +306,25 @@ readOnly = require("some-decorator"); 总之,由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。 +另一方面,如果一定要修饰函数,可以采用高阶函数的形式直接执行。 + +```javascript +function doSomething(name) { + console.log('Hello, ' + name); +} + +function loggingDecorator(wrapped) { + return function() { + console.log('Starting'); + const result = wrapped.apply(this, arguments); + console.log('Finished'); + return result; + } +} + +const wrapped = loggingDecorator(doSomething); +``` + ## core-decorators.js [core-decorators.js](https://github.com/jayphelps/core-decorators.js)是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器。 From ce1274d436467833f1087d23639c266cde13a3a0 Mon Sep 17 00:00:00 2001 From: Cody Chan Date: Thu, 8 Jun 2017 01:09:22 +0800 Subject: [PATCH 060/768] docs: update React example in ES6 properly --- docs/style.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/style.md b/docs/style.md index 33696789b..6fbb89baf 100644 --- a/docs/style.md +++ b/docs/style.md @@ -427,13 +427,13 @@ module.exports = Breadcrumbs; // ES6的写法 import React from 'react'; -const Breadcrumbs = React.createClass({ +class Breadcrumbs extends React.Component { render() { return