From 66146a6e064dc458a61304fb36c2945864d99388 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Thu, 4 Dec 2014 09:05:18 +0800 Subject: [PATCH 0001/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9module=E9=83=A8?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 193 ++++++++++++++++++++++++++++++++++++++-------- docs/reference.md | 2 + 2 files changed, 163 insertions(+), 32 deletions(-) diff --git a/docs/class.md b/docs/class.md index a723fe581..ac24218e3 100644 --- a/docs/class.md +++ b/docs/class.md @@ -73,63 +73,71 @@ class ColorPoint extends Point { ## Module的基本用法 -**(1)export和import** +JavaScript没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby的require、Python的import,甚至就连CSS都有@import,但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。 -ES6实现了模块功能,试图解决JavaScript代码的依赖和部署上的问题,取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。 +ES6解决了这个问题,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。 -模块功能有两个关键字:export和import。export用于用户自定义模块,规定对外接口;import用于输入其他模块提供的功能,同时创造命名空间(namespace),防止函数名冲突。 +**(1)export命令,import命令** + +模块功能有两个命令:export和import。export命令用于用户自定义模块,规定对外接口;import命令用于输入其他模块提供的功能,同时创造命名空间(namespace),防止函数名冲突。 ES6允许将独立的JS文件作为模块,也就是说,允许一个JavaScript脚本文件调用另一个脚本文件。该文件内部的所有变量,外部无法获取,必须使用export关键字输出变量。下面是一个JS文件,里面使用export关键字输出变量。 ```javascript // profile.js -export var firstName = 'David'; -export var lastName = 'Belle'; -export var year = 1973; +export var firstName = 'Michael'; +export var lastName = 'Jackson'; +export var year = 1958; ``` -上面是profile.js文件,ES6将其视为一个模块,里面用export关键字输出了三个变量。export的写法,除了像上面这样,还有另外一种。 +上面代码是profile.js文件,保存了用户信息。ES6将其视为一个模块,里面用export命令对外部输出了三个变量。 + +export的写法,除了像上面这样,还有另外一种。 ```javascript // profile.js -var firstName = 'David'; -var lastName = 'Belle'; -var year = 1973; +var firstName = 'Michael'; +var lastName = 'Jackson'; +var year = 1958; export {firstName, lastName, year}; ``` -上面代码在export关键字后,使用大括号输出一组变量,它与前一种写法是等价的。 +上面代码在export命令后面,使用大括号指定所要输出的一组变量。 + +它与前一种export命令直接放置在var语句前的写法是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。 -使用export定义模块以后,其他JS文件就可以通过import关键字加载这个模块(文件)。 +使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。 ```javascript +// main.js + import {firstName, lastName, year} from './profile'; -function setHeader(element) { +function sfirsetHeader(element) { element.textContent = firstName + ' ' + lastName; } ``` -上面代码中import关键字接受一个对象(用大括号表示),里面指定要从其他模块导入的变量。大括号里面的变量名,必须与被导入模块对外接口的名称相同。 +上面代码属于另一个文件main.js,import命令就用于加载profile.js文件,并从中输入变量。import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。 -如果想为输入的属性或方法重新取一个名字,import语句要写成下面这样。 +如果想为输入的变量重新取一个名字,import语句中要使用as关键字,将输入的变量重命名。 ```javascript -import { someMethod, another as newName } from './exporter'; +import { lastName as surname } from './profile'; ``` -**(2)模块的整体加载** +**(2)模块的整体输入,module命令** -export关键字除了输出变量,还可以输出方法或类(class)。下面是一个circle.js文件,它输出两个方法。 +export命令除了输出变量,还可以输出方法或类(class)。下面是一个circle.js文件,它输出两个方法area和circumference。 ```javascript @@ -145,7 +153,7 @@ export function circumference(radius) { ``` -然后,main.js引用这个模块。 +然后,main.js输入circlek.js模块。 ```javascript @@ -158,7 +166,7 @@ console.log("圆周长:" + circumference(14)); ``` -上面写法是逐一指定要导入的方法。另一种写法是整体导入。 +上面写法是逐一指定要输入的方法。另一种写法是整体输入。 ```javascript @@ -169,7 +177,7 @@ console.log("圆周长:" + circle.circumference(14)); ``` -module关键字可以取代import语句,达到整体输入模块的作用。 +module命令可以取代import语句,达到整体输入模块的作用。 ```javascript @@ -182,25 +190,23 @@ console.log("圆周长:" + circle.circumference(14)); ``` -module关键字后面跟一个变量,表示导入的模块定义在该变量上。 +module命令后面跟一个变量,表示输入的模块定义在该变量上。 -**(3)export default语句** +**(3)export default命令** -如果不想为某个属性或方法,指定输入的名称,可以使用export default语句。 +如果想要输出匿名函数,可以使用export default命令。 ```javascript // export-default.js -export default function foo() { +export default function () { console.log('foo'); } ``` -上面代码中的foo方法,就被称为该模块的默认方法。 - -在其他模块导入该模块时,import语句可以为默认方法指定任意名字。 +其他模块输入该模块时,import命令可以为该匿名函数指定任意名字。 ```javascript @@ -212,9 +218,41 @@ customName(); // 'foo' ``` -显然,一个模块只能有一个默认方法。 +上面代码的import命令,可以用任意名称指向输出的匿名函数。需要注意的是,这时import命令后面,不使用大括号。 -如果想在一条import语句中,同时输入默认方法和指定名称的变量,可以写成下面这样。 +export default命令用在非匿名函数前,也是可以的。 + +```javascript + +// export-default.js + +export default function foo() { + console.log('foo'); +} + +// 或者写成 + +function foo() { + console.log('foo'); +} + +export default foo; + +``` + +上面代码中,foo函数的函数名foo,在模块外部是无效的。加载的时候,视同匿名函数加载。 + +export default命令用于指定模块的默认输出。如果模块加载时,只能输出一个值或方法,那就是export default所指定的那个值或方法。所以,import命令后面才不用加大括号。显然,一个模块只能有一个默认输出,因此export deault命令只能使用一次。 + +有了export default命令,输入模块时就非常直观了,以输入jQuery模块为例。 + +```javascript + +import $ from 'jquery'; + +``` + +如果想在一条import语句中,同时输入默认方法和其他变量,可以写成下面这样。 ```javascript @@ -222,7 +260,7 @@ import customName, { otherMethod } from './export-default'; ``` -如果要输出默认属性,只需将值跟在`export default`之后即可。 +如果要输出默认的值,只需将值跟在`export default`之后即可。 ```javascript @@ -262,14 +300,18 @@ export default function(x) { 上面代码中的“export *”,表示输出circle模块的所有属性和方法,export default命令定义模块的默认方法。 -这时,可以为circle中的属性或方法,改名后再输出。 +这时,也可以将circle的属性或方法,改名后再输出。 ```javascript +// circleplus.js + export { area as circleArea } from 'circle'; ``` +上面代码表示,只输出circle模块的area方法,且将其改名为circleArea。 + 加载上面模块的写法如下。 ```javascript @@ -283,3 +325,90 @@ console.log(exp(math.pi)); ``` 上面代码中的"import exp"表示,将circleplus模块的默认方法加载为exp方法。 + +## ES6模块的转码 + +浏览器目前还不支持ES6模块,为了现在就能使用,可以将转为ES5的写法。 + +**(1)ES6 module transpiler** + +[ES6 module transpiler](https://github.com/esnext/es6-module-transpiler)是square公司开源的一个转码器,可以将ES6模块转为CommonJS模块或AMD模块的写法,从而在浏览器中使用。 + +首先,安装这个转玛器。 + +```bash + +$ npm install -g es6-module-transpiler + +``` + +然后,使用`compile-modules convert`命令,将ES6模块文件转码。 + +```bash + +$ compile-modules convert file1.js file2.js + +``` + +o参数可以指定转码后的文件名。 + +```bash + +$ compile-modules convert -o out.js file1.js + +``` + +**(2)SystemJS** + +另一种解决方法是使用[SystemJS](https://github.com/systemjs/systemjs)。它是一个垫片库(polyfill),可以在浏览器内加载ES6模块、AMD模块和CommonJS模块,将其转为ES5格式。它在后台调用的是Google的Traceur转码器。 + +使用时,先在网页内载入system.js文件。 + +```html + + + +``` + +然后,使用`System.import`方法加载模块文件。 + +```html + + + +``` + +上面代码中的`./app`,指的是当前目录下的app.js文件。它可以是ES6模块文件,`System.import`会自动将其转码。 + +需要注意的是,`System.import`使用异步加载,返回一个Promise对象,可以针对这个对象编程。下面是一个模块文件。 + +```javascript + +// app/es6-file.js: + +export class q { + constructor() { + this.es6 = 'hello'; + } +} + + +``` + +然后,在网页内加载这个模块文件。 + +```html + + + +``` + +上面代码中,`System.import`方法返回的是一个Promise对象,所以可以用then方法指定回调函数。 diff --git a/docs/reference.md b/docs/reference.md index 2cd53cf4e..d0704f192 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -34,6 +34,7 @@ - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 - Jack Franklin, [An introduction to ES6 classes](http://javascriptplayground.com/blog/2014/07/introduction-to-es6-classes-tutorial/): ES6 class的入门介绍 +- Jack Franklin, [JavaScript Modules the ES6 Way](http://24ways.org/2014/javascript-modules-the-es6-way/): ES6模块入门 ## Generator @@ -65,3 +66,4 @@ - army8735, [Javascript Downcast](https://github.com/army8735/jsdc): 国产的ES6到ES5的转码器 - esnext, [ES6 Module Transpiler](https://github.com/esnext/es6-module-transpiler):基于node.js的将ES6模块转为ES5代码的命令行工具 - Sebastian McKenzie, [6to5](https://github.com/sebmck/6to5): 将ES6转为ES5代码的Node模块,支持source map +- SystemJS, [SystemJS](https://github.com/systemjs/systemjs): 在浏览器中加载AMD、CJS、ES6模块的一个垫片库 From 97fcf5d921aa6d23a13f04b4d38b6e063adbe301 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Wed, 10 Dec 2014 22:30:47 +0800 Subject: [PATCH 0002/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9string/unicode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/string.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/string.md b/docs/string.md index 59b0249f6..257fff72e 100644 --- a/docs/string.md +++ b/docs/string.md @@ -4,7 +4,7 @@ ES6加强了对Unicode的支持,并且扩展了字符串对象。 ## codePointAt() -JavaScript内部,字符以UTF-16的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode编号大于0xFFFF的字符),JavaScript会认为它们是两个字符。 +JavaScript内部,字符以UTF-16的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode码点大于0xFFFF的字符),JavaScript会认为它们是两个字符。 ```javascript @@ -18,9 +18,9 @@ s.charCodeAt(1) // 57271 ``` -上面代码中,汉字“𠮷”的Unicode编号是0x20BB7,UTF-16编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。对于这种4个字节的字符,JavaScript不能正确处理,字符串长度会误判为2,而且charAt方法无法读取字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。 +上面代码中,汉字“𠮷”的码点是0x20BB7,UTF-16编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。对于这种4个字节的字符,JavaScript不能正确处理,字符串长度会误判为2,而且charAt方法无法读取字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。 -ES6提供了codePointAt方法,能够正确处理4个字节储存的字符,返回一个字符的Unicode编号。 +ES6提供了codePointAt方法,能够正确处理4个字节储存的字符,返回一个字符的码点。 ```javascript @@ -33,9 +33,9 @@ s.charCodeAt(2) // 97 ``` -codePointAt方法的参数,是字符在字符串中的位置(从0开始)。上面代码中,JavaScript将“𠮷a”视为三个字符,codePointAt方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制Unicode编号134071(即十六进制的20BB7)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt方法的结果与charCodeAt方法相同。 +codePointAt方法的参数,是字符在字符串中的位置(从0开始)。上面代码中,JavaScript将“𠮷a”视为三个字符,codePointAt方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点134071(即十六进制的20BB7)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt方法的结果与charCodeAt方法相同。 -总之,codePointAt方法会正确返回四字节的UTF-16字符的Unicode编号。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt方法相同。 +总之,codePointAt方法会正确返回四字节的UTF-16字符的码点。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt方法相同。 codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。 @@ -52,7 +52,7 @@ is32Bit("a") // false ## String.fromCodePoint() -ES5提供String.fromCharCode方法,用于从Unicode编号返回对应字符,但是这个方法不能识别辅助平面的字符(编号大于0xFFFF)。 +ES5提供String.fromCharCode方法,用于从码点返回对应字符,但是这个方法不能识别辅助平面的字符(编号大于0xFFFF)。 ```javascript @@ -61,7 +61,7 @@ String.fromCharCode(0x20BB7) ``` -上面代码中,最后返回的字符编号是0x0BB7,而不是0x20BB7。 +上面代码中,最后返回码点U+0BB7对应的字符,而不是码点U+20BB7对应的字符。 ES6提供了String.fromCodePoint方法,可以识别0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。 @@ -76,7 +76,7 @@ String.fromCodePoint(0x20BB7) ## at() -ES5提供String.prototype.charAt方法,返回字符串给定位置的字符。该方法不能识别Unicode编号大于0xFFFF的字符。 +ES5提供String.prototype.charAt方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF的字符。 ```javascript @@ -98,7 +98,7 @@ ES7提供了at方法,可以识别Unicode编号大于0xFFFF的字符,返回 ## 字符的Unicode表示法 -JavaScript允许采用“\uxxxx”形式表示一个字符,其中“xxxx”表示字符的Unicode编号。 +JavaScript允许采用“\uxxxx”形式表示一个字符,其中“xxxx”表示字符的码点。 ```javascript @@ -121,7 +121,7 @@ JavaScript允许采用“\uxxxx”形式表示一个字符,其中“xxxx”表 上面代码表示,如果直接在“\u”后面跟上超过0xFFFF的数值(比如\u20BB7),JavaScript会理解成“\u20BB+7”。由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。 -ES6对这一点做出了改进,只要将Unicode编号放入大括号,就能正确解读该字符。 +ES6对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。 ```javascript @@ -139,7 +139,7 @@ ES6对正则表达式添加了u修饰符,用来正确处理大于\uFFFF的Unic **(1)点字符** -点(.)字符在正则表达式中,解释为除了换行以外的任意单个字符。对于大于\uFFFF的Unicode字符,点字符不能识别,必须加上u修饰符。 +点(.)字符在正则表达式中,解释为除了换行以外的任意单个字符。对于码点大于0xFFFF的Unicode字符,点字符不能识别,必须加上u修饰符。 ```javascript @@ -164,11 +164,11 @@ ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达 ``` -上面代码表示,如果不加u修饰符,正则表达式无法识别`\u{61}`这种表示法,只会认为这匹配属61个连续的u。 +上面代码表示,如果不加u修饰符,正则表达式无法识别`\u{61}`这种表示法,只会认为这匹配61个连续的u。 **(3)量词** -使用u修饰符后,所有量词都会正确识别大于\uFFFF的Unicode字符。 +使用u修饰符后,所有量词都会正确识别大于码点大于0xFFFF的Unicode字符。 ```javascript @@ -181,7 +181,7 @@ ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达 **(4)预定义模式** -u修饰符也影响到预定义模式,能否正确识别大于\uFFFF的Unicode字符。 +u修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的Unicode字符。 ```javascript @@ -190,7 +190,7 @@ u修饰符也影响到预定义模式,能否正确识别大于\uFFFF的Unicode ``` -上面代码的`\S`是预定义模式,匹配所有不是空格的字符。只有加了u修饰符,它才能正确匹配大于\uFFFF的Unicode字符。 +上面代码的`\S`是预定义模式,匹配所有不是空格的字符。只有加了u修饰符,它才能正确匹配码点大于0xFFFF的Unicode字符。 利用这一点,可以写出一个正确返回字符串长度的函数。 From 5e9968e8989fe4b9545ec57b8fb76672da74b0b7 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 12 Dec 2014 08:40:44 +0800 Subject: [PATCH 0003/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9docs/class/?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 10 ++++------ docs/reference.md | 8 ++++++-- docs/string.md | 22 ++++++++++++++++++---- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/docs/class.md b/docs/class.md index ac24218e3..42202055b 100644 --- a/docs/class.md +++ b/docs/class.md @@ -75,11 +75,11 @@ class ColorPoint extends Point { JavaScript没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby的require、Python的import,甚至就连CSS都有@import,但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。 -ES6解决了这个问题,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。 +在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。 **(1)export命令,import命令** -模块功能有两个命令:export和import。export命令用于用户自定义模块,规定对外接口;import命令用于输入其他模块提供的功能,同时创造命名空间(namespace),防止函数名冲突。 +模块功能主要由两个命令构成:export和import。export命令用于用户自定义模块,规定对外接口;import命令用于输入其他模块提供的功能,同时创造命名空间(namespace),防止函数名冲突。 ES6允许将独立的JS文件作为模块,也就是说,允许一个JavaScript脚本文件调用另一个脚本文件。该文件内部的所有变量,外部无法获取,必须使用export关键字输出变量。下面是一个JS文件,里面使用export关键字输出变量。 @@ -107,9 +107,7 @@ export {firstName, lastName, year}; ``` -上面代码在export命令后面,使用大括号指定所要输出的一组变量。 - -它与前一种export命令直接放置在var语句前的写法是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。 +上面代码在export命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在var语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。 使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。 @@ -213,7 +211,6 @@ export default function () { // import-default.js import customName from './export-default'; - customName(); // 'foo' ``` @@ -277,6 +274,7 @@ export default class { ... } // main.js import MyClass from 'MyClass' +let o = new MyClass(); ``` diff --git a/docs/reference.md b/docs/reference.md index d0704f192..9f772708e 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -33,8 +33,6 @@ - Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 -- Jack Franklin, [An introduction to ES6 classes](http://javascriptplayground.com/blog/2014/07/introduction-to-es6-classes-tutorial/): ES6 class的入门介绍 -- Jack Franklin, [JavaScript Modules the ES6 Way](http://24ways.org/2014/javascript-modules-the-es6-way/): ES6模块入门 ## Generator @@ -56,6 +54,12 @@ - Jafar Husain, [Async Generators](https://docs.google.com/file/d/0B4PVbLpUIdzoMDR5dWstRllXblU/view?sle=true): 对async与Generator混合使用的一些讨论 - Axel Rauschmayer, [ECMAScript 6 promises (2/2): the API](http://www.2ality.com/2014/10/es6-promises-api.html): 对ES6 Promise规格和用法的详细介绍 +## Class与模块 + +- Jack Franklin, [An introduction to ES6 classes](http://javascriptplayground.com/blog/2014/07/introduction-to-es6-classes-tutorial/): ES6 class的入门介绍 +- Jack Franklin, [JavaScript Modules the ES6 Way](http://24ways.org/2014/javascript-modules-the-es6-way/): ES6模块入门 +- Axel Rauschmayer, [ECMAScript 6 modules: the final syntax](http://www.2ality.com/2014/09/es6-modules-final.html): ES6模块的介绍,以及与CommonJS规格的详细比较 + ## 工具 - Google, [traceur-compiler](https://github.com/google/traceur-compiler): Traceur编译器 diff --git a/docs/string.md b/docs/string.md index 257fff72e..7c6b403bf 100644 --- a/docs/string.md +++ b/docs/string.md @@ -379,14 +379,28 @@ r.sticky // true var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?` +``` + +上面代码中的字符串,都是用反引号表示。如果在模板字符串中嵌入变量,需要将变量名写在`${}`之中。 + +大括号内部可以进行运算,以及引用对象属性。 + +```javascript + var x = 1; var y = 2; -console.log(`${ x } + ${ y } = ${ x + y}`) + +console.log(`${x} + ${y} = ${x+y}`) // "1 + 2 = 3" - -``` -上面代码表示,在模板字符串中嵌入变量,需要将变量名写在${}之中。 +console.log(`${x} + ${y*2} = ${x+y*2}`) +// "1 + 4 = 5" + +var obj = {x: 1, y: 2}; +console.log(`${obj.x + obj.y}`) +// 3 + +``` 模板字符串使得字符串与变量的结合,变得容易。下面是一个例子。 From 4cf70a17ab763c800dd3f42f1672fe4b3e7a689d Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 14 Dec 2014 12:33:41 +0800 Subject: [PATCH 0004/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 18 ++++++++++++++++++ docs/reference.md | 1 + 2 files changed, 19 insertions(+) diff --git a/docs/class.md b/docs/class.md index 42202055b..d94472d2f 100644 --- a/docs/class.md +++ b/docs/class.md @@ -77,6 +77,24 @@ JavaScript没有模块(module)体系,无法将一个大程序拆分成互 在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。 +ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。比如,CommonJS模块就是对象,输入时必须查找对象属性。 + +```javascript + +var { stat, exists, readFile } = require('fs'); + +``` + +ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式。 + +```javascript + +import { stat, exists, readFile } from 'fs'; + +``` + +所以,ES6可以在编译时就完成模块编译,效率要比CommonJS模块高。 + **(1)export命令,import命令** 模块功能主要由两个命令构成:export和import。export命令用于用户自定义模块,规定对外接口;import命令用于输入其他模块提供的功能,同时创造命名空间(namespace),防止函数名冲突。 diff --git a/docs/reference.md b/docs/reference.md index 9f772708e..f2d4faae9 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -59,6 +59,7 @@ - Jack Franklin, [An introduction to ES6 classes](http://javascriptplayground.com/blog/2014/07/introduction-to-es6-classes-tutorial/): ES6 class的入门介绍 - Jack Franklin, [JavaScript Modules the ES6 Way](http://24ways.org/2014/javascript-modules-the-es6-way/): ES6模块入门 - Axel Rauschmayer, [ECMAScript 6 modules: the final syntax](http://www.2ality.com/2014/09/es6-modules-final.html): ES6模块的介绍,以及与CommonJS规格的详细比较 +- Dave Herman, [Static module resolution](http://calculist.org/blog/2012/06/29/static-module-resolution/): ES6模块的静态化设计思想 ## 工具 From e635b182f60cbd7765ab2c0828715775431e9474 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Thu, 18 Dec 2014 22:24:14 +0800 Subject: [PATCH 0005/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 88 +++++++++++++++++++++++++++++++++++------------ docs/reference.md | 1 + 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index b8e502cea..f0d762e8b 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -2,15 +2,15 @@ ## 含义 -所谓Generator,简单说,就是一个内部状态的遍历器,即每调用一次遍历器,内部状态发生一次改变(可以理解成发生某些事件)。ES6引入Generator函数,作用就是可以完全控制内部状态的变化,依次遍历这些状态。 +所谓Generator,有多种理解角度。首先,可以把它理解成一个内部状态的遍历器,即每调用一次遍历器,内部状态发生一次改变(可以理解成发生某些事件)。ES6引入Generator函数,作用就是可以完全控制内部状态的变化,依次遍历这些状态。 -Generator函数就是普通函数,但是有两个特征。一是,function关键字后面有一个星号;二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态(yield语句在英语里的意思就是“产出”)。 +在形式上,Generator是一个普通函数,但是有两个特征。一是,function命令与函数名之间有一个星号;二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态(yield语句在英语里的意思就是“产出”)。 ```javascript function* helloWorldGenerator() { - yield 'hello'; - yield 'world'; + yield 'hello'; + yield 'world'; return 'ending'; } @@ -50,9 +50,7 @@ hw.next() 总结一下,Generator函数使用iterator接口,每次调用next方法的返回值,就是一个标准的iterator返回值:有着value和done两个属性的对象。其中,value是yield语句后面那个表达式的值,done是一个布尔值,表示是否遍历结束。 -Generator函数的本质,其实是提供一种可以暂停执行的函数。yield语句就是暂停标志,next方法遇到yield,就会暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性的值。当下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。如果没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,作为value属性的值,如果该函数没有return语句,则value属性的值为undefined。 - -由于yield后面的表达式,直到调用next方法时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。 +由于Generator函数返回的遍历器,只有调用next方法才会遍历下一个成员,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志,next方法遇到yield,就会暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性的值。当下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。如果没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,作为value属性的值,如果该函数没有return语句,则value属性的值为undefined。另一方面,由于yield后面的表达式,直到调用next方法时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。 yield语句与return语句有点像,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。 @@ -72,7 +70,7 @@ setTimeout(function () { ``` -上面代码中,只有调用next方法时,函数f才会执行。 +上面代码中,函数f如果是普通函数,在为generator变量赋值时就会执行。但是,函数f是一个Generator函数,就变成只有调用next方法时,函数f才会执行。 利用Generator函数,可以在任意对象上部署iterator接口。 @@ -97,7 +95,7 @@ for (let [key, value] of iterEntries(myObj)) { ``` -上述代码中,由于Generator函数返回一个具有iterator接口的对象,所以只要让yield语句每次返回一个参数对象的成员,就可以在任意对象上部署next方法。 +上述代码中,myObj是一个普通对象,通过iterEntries函数,就有了iterator接口。也就是说,可以在任意对象上部署next方法。 ## next方法的参数 @@ -234,8 +232,8 @@ function scheduler(task) { setTimeout(function () { if (!task.next().done) { scheduler(task); - } - }, 0); + } + }, 0); } ``` @@ -247,13 +245,13 @@ function scheduler(task) { var Q = require('q'); function delay(milliseconds) { - var deferred = Q.defer(); - setTimeout(deferred.resolve, milliseconds); - return deferred.promise; + var deferred = Q.defer(); + setTimeout(deferred.resolve, milliseconds); + return deferred.promise; } function* f(){ - yield delay(100); + yield delay(100); }; ``` @@ -267,16 +265,16 @@ for...of循环可以自动遍历Generator函数,且此时不再需要调用nex ```javascript function *foo() { - yield 1; - yield 2; - yield 3; - yield 4; - yield 5; - return 6; + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + return 6; } for (let v of foo()) { - console.log(v); + console.log(v); } // 1 2 3 4 5 @@ -305,6 +303,52 @@ for (let n of fibonacci()) { 从上面代码可见,使用for...of语句时不需要使用next方法。 +## 作为数据结构的Generator + +Generator可以暂停函数执行,返回任意表达式的值。这种特点使得Generator有多种应用场景。 + +- 异步操作的同步化表达(abstractions of async behavior) +- 控制流管理(control flow management) +- 数据结构(data structure) + +第一种和第二种应用在本章前面部分,已经有所提及了。这里主要再补充一下,Generator可以看作是数据结构,因为它可以对任意表达式,提供类似数组的接口。 + +```javascript + +function *doStuff() { + yield fs.readFile.bind(null, 'hello.txt'); + yield fs.readFile.bind(null, 'world.txt'); + yield fs.readFile.bind(null, 'and-such.txt'); +} + +``` + +上面代码就是依次返回三个函数,但是由于使用了Generator函数,导致可以像处理数组那样,处理这三个返回的函数。 + +```javascript + +for (task of doStuff()) { + // task是一个函数,可以像回调函数那样使用它 +} + +``` + +实际上,如果用ES5表达,完全可以用数组模拟Generator的这种用法。 + +```javascript + +function doStuff() { + return [ + fs.readFile.bind(null, 'hello.txt'), + fs.readFile.bind(null, 'world.txt'), + fs.readFile.bind(null, 'and-such.txt') + ]; +} + +``` + +上面的函数,可以用一模一样的for...of循环处理!两相一比较,就不难看出Generator的这种用法,就是让数据或者操作,可以像数组那样处理。 + ## throw方法 Generator函数还有一个特点,它可以在函数体外抛出错误,然后在函数体内捕获。 diff --git a/docs/reference.md b/docs/reference.md index f2d4faae9..879fd3b21 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -45,6 +45,7 @@ - StackOverflow, [ES6 yield : what happens to the arguments of the first call next()?](http://stackoverflow.com/questions/20977379/es6-yield-what-happens-to-the-arguments-of-the-first-call-next): 第一次使用next方法时不能带有参数 - Kyle Simpson, [ES6 Generators: Complete Series](http://davidwalsh.name/es6-generators): 由浅入深探讨Generator的系列文章,共四篇 - Gajus Kuizinas, [The Definitive Guide to the JavaScript Generators](http://gajus.com/blog/2/the-definetive-guide-to-the-javascript-generators): 对Generator的综合介绍 +- Jan Krems, [Generators Are Like Arrays](https://gist.github.com/jkrems/04a2b34fb9893e4c2b5c): 讨论Generator可以被当作数据结构看待 ## Promise对象 From 8b3d0a575d91f13c8b794485e79ce00bb4a6c853 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 21 Dec 2014 22:16:37 +0800 Subject: [PATCH 0006/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9docs/class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 2 ++ docs/generator.md | 2 +- docs/reference.md | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/class.md b/docs/class.md index d94472d2f..f8832e9d9 100644 --- a/docs/class.md +++ b/docs/class.md @@ -71,6 +71,8 @@ class ColorPoint extends Point { 上面代码中,constructor方法和toString方法之中,都出现了super关键字,它指代父类的同名方法。在constructor方法内,super指代父类的constructor方法;在toString方法内,super指代父类的toString方法。 +有一个地方,需要注意。类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。 + ## Module的基本用法 JavaScript没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby的require、Python的import,甚至就连CSS都有@import,但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。 diff --git a/docs/generator.md b/docs/generator.md index f0d762e8b..686a7d0fd 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -347,7 +347,7 @@ function doStuff() { ``` -上面的函数,可以用一模一样的for...of循环处理!两相一比较,就不难看出Generator的这种用法,就是让数据或者操作,可以像数组那样处理。 +上面的函数,可以用一模一样的for...of循环处理!两相一比较,就不难看出Generator使得数据或者操作,具备了类似数组的接口。 ## throw方法 diff --git a/docs/reference.md b/docs/reference.md index 879fd3b21..b3bfe2d55 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -4,6 +4,7 @@ - [ECMAScript 6 Language Specification](http://people.mozilla.org/~jorendorff/es6-draft.html): 语言规格草案 - [harmony:proposals](http://wiki.ecmascript.org/doku.php?id=harmony:proposals): ES6的各种提案 +- [Draft Specification for ES.next (Ecma-262 Edition 6)](http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts): ES6草案各版本之间的变动 ## 综合介绍 From ef6bd9d6fa2cb974f7db43afa022393178bc91c3 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 23 Dec 2014 08:55:24 +0800 Subject: [PATCH 0007/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9object/assign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 26 +++++ docs/object.md | 263 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 221 insertions(+), 68 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index 686a7d0fd..ae262c9e2 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -499,3 +499,29 @@ result // ['a', 'b', 'c', 'd', 'e', 'f', 'g'] ``` + +## 作为对象属性的Generator函数 + +如果一个对象的属性是Generator函数,可以简写成下面的形式。 + +```javascript + +let obj = { + * myGeneratorMethod() { + ··· + } +}; + +``` + +它的完整形式如下,两者是等价的。 + +```javascript + +let obj = { + myGeneratorMethod: function* () { + ··· + } +}; + +``` diff --git a/docs/object.md b/docs/object.md index 61abb1bde..60986c8cd 100644 --- a/docs/object.md +++ b/docs/object.md @@ -1,5 +1,124 @@ # 对象的扩展 +## 属性的简洁表示法 + +ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。 + +```javascript + +var Person = { + + name: '张三', + + //等同于birth: birth + birth, + + // 等同于hello: function ()... + hello() { console.log('我的名字是', this.name); } + +}; + +``` + +这种写法用于函数的返回值,将会非常方便。 + +```javascript + +function getPoint() { + var x = 1; + var y = 10; + + return {x, y}; +} + +getPoint() +// {x:1, y:10} + +``` + +下面是一个类似的例子。 + +```javascript + +let x = 4; +let y = 1; + +// 下行等同于 let obj = { x: x, y: y }; +let obj = { x, y }; + +``` + +## 属性名表达式 + +JavaScript语言定义对象的属性,有两种方法。 + +```javascript + +// 方法一 +obj.foo = true; + +// 方法二 +obj['a'+'bc'] = 123; + +``` + +上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。 + +但是,如果使用字面量方式定义对象(使用大括号),在ES5中只能使用方法一(标识符)定义属性。 + +```javascript + +var obj = { + foo: true, + abc: 123 +}; + +``` + +ES6允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。 + +```javascript + +let propKey = 'foo'; + +let obj = { + [propKey]: true, + ['a'+'bc']: 123 +}; + +``` + +下面是另一个例子。 + +```javascript + +var lastWord = "last word"; + +var a = { + "first word": "hello", + [lastWord]: "world" +}; + +a["first word"] // "hello" +a[lastWord] // "world" +a["last word"] // "world" + +``` + +表达式还可以用于定义方法名。 + +```javascript + +let obj = { + ['h'+'ello']() { + return 'hi'; + } +}; + +console.log(obj.hello()); // hi + +``` + ## Object.is() Object.is()用来比较两个值是否严格相等。它与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身。 @@ -44,135 +163,143 @@ target // {a:1, b:2, c:3} ``` -## __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf() - -**(1)__proto__属性** +assign方法有很多用处。 -__proto__属性,用来读取或设置当前对象的prototype对象。该属性一度被正式写入ES6草案,但后来又被移除。目前,所有浏览器(包括IE11)都部署了这个属性。 +**(1)为对象添加属性** ```javascript -// es6的写法 - -var obj = { - __proto__: someOtherObj, - method: function() { ... } +class Point { + constructor(x, y) { + Object.assign(this, {x, y}); + } } -// es5的写法 - -var obj = Object.create(someOtherObj); -obj.method = function() { ... } - ``` -有了这个属性,实际上已经不需要通过Object.create()来生成新对象了。 +上面方法通过assign方法,将x属性和y属性添加到Point类的对象实例。 -**(2)Object.setPrototypeOf()** - -Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。 +**(2)为对象添加方法** ```javascript -// 格式 -Object.setPrototypeOf(object, prototype) +Object.assign(SomeClass.prototype, { + someMethod(arg1, arg2) { + ··· + }, + anotherMethod() { + ··· + } +}); -// 用法 -var o = Object.setPrototypeOf({}, null); +// 等同于下面的写法 +SomeClass.prototype.someMethod = function (arg1, arg2) { + ··· +}; +SomeClass.prototype.anotherMethod = function () { + ··· +}; ``` -该方法等同于下面的函数。 +上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用assign方法添加到SomeClass.prototype之中。 + +**(3)克隆对象** ```javascript -function (obj, proto) { - obj.__proto__ = proto; - return obj; +function clone(origin) { + return Object.assign({}, origin); } ``` -**(3)Object.getPrototypeOf()** +上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。 -该方法与setPrototypeOf方法配套,用于读取一个对象的prototype对象。 +不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。 ```javascript -Object.getPrototypeOf(obj) +function clone(origin) { + let originProto = Object.getPrototypeOf(origin); + return Object.assign(Object.create(originProto), origin); +} ``` -## 增强的对象写法 - -ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。 +**(4)为属性指定默认值** ```javascript -var Person = { +const DEFAULTS = { + logLevel: 0, + outputFormat: 'html' +}; - name: '张三', +function processContent(options) { + let options = Object.assign({}, DEFAULTS, options); +} - //等同于birth: birth - birth, +``` - // 等同于hello: function ()... - hello() { console.log('我的名字是', this.name); } +上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。 -}; +## __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf() -``` +**(1)__proto__属性** -这种写法用于函数的返回值,将会非常方便。 +__proto__属性,用来读取或设置当前对象的prototype对象。该属性一度被正式写入ES6草案,但后来又被移除。目前,所有浏览器(包括IE11)都部署了这个属性。 ```javascript -function getPoint() { - var x = 1; - var y = 10; +// es6的写法 - return {x, y}; +var obj = { + __proto__: someOtherObj, + method: function() { ... } } -getPoint() -// {x:1, y:10} +// es5的写法 + +var obj = Object.create(someOtherObj); +obj.method = function() { ... } ``` -## 属性名表达式 +有了这个属性,实际上已经不需要通过Object.create()来生成新对象了。 -ES6允许定义对象时,用表达式作为对象的属性名。在写法上,要把表达式放在方括号内。 +**(2)Object.setPrototypeOf()** -```javascript +Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。它是ES6正式推荐的设置原型对象的方法。 -var lastWord = "last word"; +```javascript -var a = { - "first word": "hello", - [lastWord]: "world" -}; +// 格式 +Object.setPrototypeOf(object, prototype) -a["first word"] // "hello" -a[lastWord] // "world" -a["last word"] // "world" +// 用法 +var o = Object.setPrototypeOf({}, null); ``` -上面代码中,对象a的属性名lastWord是一个变量。 - -下面是一个将字符串的加法表达式作为属性名的例子。 +该方法等同于下面的函数。 ```javascript -var suffix = " word"; +function (obj, proto) { + obj.__proto__ = proto; + return obj; +} + +``` -var a = { - ["first" + suffix]: "hello", - ["last" + suffix]: "world" -}; +**(3)Object.getPrototypeOf()** -a["first word"] // "hello" -a["last word"] // "world" +该方法与setPrototypeOf方法配套,用于读取一个对象的prototype对象。 + +```javascript + +Object.getPrototypeOf(obj) ``` From 956a8860cc19a66a6aee22df090aba1323628cd0 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 26 Dec 2014 05:44:02 +0800 Subject: [PATCH 0008/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 140 +++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 69 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index ae262c9e2..367de19dd 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -52,7 +52,7 @@ hw.next() 由于Generator函数返回的遍历器,只有调用next方法才会遍历下一个成员,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志,next方法遇到yield,就会暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性的值。当下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。如果没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,作为value属性的值,如果该函数没有return语句,则value属性的值为undefined。另一方面,由于yield后面的表达式,直到调用next方法时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。 -yield语句与return语句有点像,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。 +yield语句与return语句有点像,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。 Generator函数可以不用yield语句,这时就变成了一个单纯的暂缓执行函数。 @@ -70,32 +70,7 @@ setTimeout(function () { ``` -上面代码中,函数f如果是普通函数,在为generator变量赋值时就会执行。但是,函数f是一个Generator函数,就变成只有调用next方法时,函数f才会执行。 - -利用Generator函数,可以在任意对象上部署iterator接口。 - -```javascript - -function* iterEntries(obj) { - let keys = Object.keys(obj); - for (let i=0; i < keys.length; i++) { - let key = keys[i]; - yield [key, obj[key]]; - } -} - -let myObj = { foo: 3, bar: 7 }; - -for (let [key, value] of iterEntries(myObj)) { - console.log(key, value); -} - -// foo 3 -// bar 7 - -``` - -上述代码中,myObj是一个普通对象,通过iterEntries函数,就有了iterator接口。也就是说,可以在任意对象上部署next方法。 +上面代码中,函数f如果是普通函数,在为变量generator赋值时就会执行。但是,函数f是一个Generator函数,就变成只有调用next方法时,函数f才会执行。 ## next方法的参数 @@ -145,9 +120,58 @@ it.next(13) 注意,由于next方法的参数表示上一个yield语句的返回值,所以第一次使用next方法时,不能带有参数。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。 -## 异步操作的应用 +## for...of循环 + +for...of循环可以自动遍历Generator函数,且此时不再需要调用next方法。 + +```javascript + +function *foo() { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + return 6; +} + +for (let v of foo()) { + console.log(v); +} +// 1 2 3 4 5 + +``` + +上面代码使用for...of循环,依次显示5个yield语句的值。这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。 + +下面是一个利用generator函数和for...of循环,实现斐波那契数列的例子。 + +```javascript + +function* fibonacci() { + let [prev, curr] = [0, 1]; + for (;;) { + [prev, curr] = [curr, prev + curr]; + yield curr; + } +} + +for (let n of fibonacci()) { + if (n > 1000) break; + console.log(n); +} + +``` + +从上面代码可见,使用for...of语句时不需要使用next方法。 -Generator函数的这种暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。 +## 应用 + +Generator可以暂停函数执行,返回任意表达式的值。这种特点使得Generator有多种应用场景。 + +### (1)异步操作的同步化表达 + +Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。 ```javascript @@ -209,6 +233,8 @@ function* numbers() { 上面代码打开文本文件,使用yield语句可以手动逐行读取文件。 +### (2)控制流管理 + 总结一下,如果某个操作非常耗时,可以把它拆成N步。 ```javascript @@ -258,60 +284,36 @@ function* f(){ 上面代码使用Promise的函数库Q,yield语句返回的就是一个Promise对象。 -## for...of循环 +### (3)部署iterator接口 -for...of循环可以自动遍历Generator函数,且此时不再需要调用next方法。 +利用Generator函数,可以在任意对象上部署iterator接口。 ```javascript -function *foo() { - yield 1; - yield 2; - yield 3; - yield 4; - yield 5; - return 6; -} - -for (let v of foo()) { - console.log(v); +function* iterEntries(obj) { + let keys = Object.keys(obj); + for (let i=0; i < keys.length; i++) { + let key = keys[i]; + yield [key, obj[key]]; + } } -// 1 2 3 4 5 - -``` - -上面代码使用for...of循环,依次显示5个yield语句的值。这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。 -下面是一个利用generator函数和for...of循环,实现斐波那契数列的例子。 - -```javascript +let myObj = { foo: 3, bar: 7 }; -function* fibonacci() { - let [prev, curr] = [0, 1]; - for (;;) { - [prev, curr] = [curr, prev + curr]; - yield curr; - } +for (let [key, value] of iterEntries(myObj)) { + console.log(key, value); } -for (let n of fibonacci()) { - if (n > 1000) break; - console.log(n); -} +// foo 3 +// bar 7 ``` -从上面代码可见,使用for...of语句时不需要使用next方法。 - -## 作为数据结构的Generator - -Generator可以暂停函数执行,返回任意表达式的值。这种特点使得Generator有多种应用场景。 +上述代码中,myObj是一个普通对象,通过iterEntries函数,就有了iterator接口。也就是说,可以在任意对象上部署next方法。 -- 异步操作的同步化表达(abstractions of async behavior) -- 控制流管理(control flow management) -- 数据结构(data structure) +### (4)作为数据结构 -第一种和第二种应用在本章前面部分,已经有所提及了。这里主要再补充一下,Generator可以看作是数据结构,因为它可以对任意表达式,提供类似数组的接口。 +Generator可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为Generator函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。 ```javascript From 15547a5abc75349f497e7e4f20ee112f1c6b08ac Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 26 Dec 2014 09:08:56 +0800 Subject: [PATCH 0009/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 390 +++++++++++++++++++++++++--------------------- docs/reference.md | 1 + 2 files changed, 213 insertions(+), 178 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index 367de19dd..0815c1a54 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -2,7 +2,7 @@ ## 含义 -所谓Generator,有多种理解角度。首先,可以把它理解成一个内部状态的遍历器,即每调用一次遍历器,内部状态发生一次改变(可以理解成发生某些事件)。ES6引入Generator函数,作用就是可以完全控制内部状态的变化,依次遍历这些状态。 +所谓Generator,有多种理解角度。首先,可以把它理解成一个内部状态的遍历器,每调用一次,内部状态发生一次改变(可以理解成发生某些事件)。ES6引入Generator函数,作用就是可以完全控制内部状态的变化,依次遍历这些状态。 在形式上,Generator是一个普通函数,但是有两个特征。一是,function命令与函数名之间有一个星号;二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态(yield语句在英语里的意思就是“产出”)。 @@ -165,6 +165,217 @@ for (let n of fibonacci()) { 从上面代码可见,使用for...of语句时不需要使用next方法。 +## throw方法 + +Generator函数还有一个特点,它可以在函数体外抛出错误,然后在函数体内捕获。 + +```javascript + +var g = function* () { + while (true) { + try { + yield; + } catch (e) { + if (e != 'a') { + throw e; + } + console.log('内部捕获', e); + } + } +}; + +var i = g(); +i.next(); + +try { + i.throw('a'); + i.throw('b'); +} catch (e) { + console.log('外部捕获', e); +} +// 内部捕获 a +// 外部捕获 b + +``` + +上面代码中,遍历器i连续抛出两个错误。第一个错误被Generator函数体内的catch捕获,然后Generator函数执行完成,于是第二个错误被函数体外的catch捕获。 + +这种函数体内捕获错误的机制,大大方便了对错误的处理。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数写一个错误处理语句。 + +```javascript + +foo('a', function (a) { + if (a.error) { + throw new Error(a.error); + } + + foo('b', function (b) { + if (b.error) { + throw new Error(b.error); + } + + foo('c', function (c) { + if (c.error) { + throw new Error(c.error); + } + + console.log(a, b, c); + }); + }); +}); + +``` + +使用Generator函数可以大大简化上面的代码。 + +```javascript + +function* g(){ + try { + var a = yield foo('a'); + var b = yield foo('b'); + var c = yield foo('c'); + } catch (e) { + console.log(e); + } + + console.log(a, b, c); + +} + +``` + +## yield*语句 + +如果yield命令后面跟的是一个遍历器,需要在yield命令后面加上星号,表明它返回的是一个遍历器。这被称为yield*语句。 + +```javascript + +let delegatedIterator = (function* () { + yield 'Hello!'; + yield 'Bye!'; +}()); + +let delegatingIterator = (function* () { + yield 'Greetings!'; + yield* delegatedIterator; + yield 'Ok, bye.'; +}()); + +for(let value of delegatingIterator) { + console.log(value); +} +// "Greetings! +// "Hello!" +// "Bye!" +// "Ok, bye." + +``` + +上面代码中,delegatingIterator是代理者,delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Genertor函数,有递归的效果。 + +下面是一个稍微复杂的例子,使用yield*语句遍历完全二叉树。 + +```javascript + +// 下面是二叉树的构造函数, +// 三个参数分别是左树、当前节点和右树 +function Tree(left, label, right) { + this.left = left; + this.label = label; + this.right = right; +} + +// 下面是中序(inorder)遍历函数。 +// 由于返回的是一个遍历器,所以要用generator函数。 +// 函数体内采用递归算法,所以左树和右树要用yield*遍历 +function* inorder(t) { + if (t) { + yield* inorder(t.left); + yield t.label; + yield* inorder(t.right); + } +} + +// 下面生成二叉树 +function make(array) { + // 判断是否为叶节点 + if (array.length == 1) return new Tree(null, array[0], null); + return new Tree(make(array[0]), array[1], make(array[2])); +} +let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]); + +// 遍历二叉树 +var result = []; +for (let node of inorder(tree)) { + result.push(node); +} + +result +// ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + +``` + +## 作为对象属性的Generator函数 + +如果一个对象的属性是Generator函数,可以简写成下面的形式。 + +```javascript + +let obj = { + * myGeneratorMethod() { + ··· + } +}; + +``` + +它的完整形式如下,两者是等价的。 + +```javascript + +let obj = { + myGeneratorMethod: function* () { + ··· + } +}; + +``` + +## Generator与状态机 + +Generator是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。 + +```javascript + +var ticking = true; +var clock = function() { + if (ticking) + console.log('Tick!'); + else + console.log('Tock!'); + ticking = !ticking; +} + +``` + +上面代码的clock函数一共有两种状态(Tick和Tock),每运行一次,就改变一次状态。这个函数如果用Generator实现,就是下面这样。 + +```javascript + +var clock = function*(_) { + while (true) { + yield _; + console.log('Tick!'); + yield _; + console.log('Tock!'); + } +}; + +``` + +上面的Generator实现与ES5实现对比,可以看到少了用来保存状态的外部变量ticking,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。 + ## 应用 Generator可以暂停函数执行,返回任意表达式的值。这种特点使得Generator有多种应用场景。 @@ -350,180 +561,3 @@ function doStuff() { ``` 上面的函数,可以用一模一样的for...of循环处理!两相一比较,就不难看出Generator使得数据或者操作,具备了类似数组的接口。 - -## throw方法 - -Generator函数还有一个特点,它可以在函数体外抛出错误,然后在函数体内捕获。 - -```javascript - -var g = function* () { - while (true) { - try { - yield; - } catch (e) { - if (e != 'a') { - throw e; - } - console.log('内部捕获', e); - } - } -}; - -var i = g(); -i.next(); - -try { - i.throw('a'); - i.throw('b'); -} catch (e) { - console.log('外部捕获', e); -} -// 内部捕获 a -// 外部捕获 b - -``` - -上面代码中,遍历器i连续抛出两个错误。第一个错误被Generator函数体内的catch捕获,然后Generator函数执行完成,于是第二个错误被函数体外的catch捕获。 - -这种函数体内捕获错误的机制,大大方便了对错误的处理。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数写一个错误处理语句。 - -```javascript - -foo('a', function (a) { - if (a.error) { - throw new Error(a.error); - } - - foo('b', function (b) { - if (b.error) { - throw new Error(b.error); - } - - foo('c', function (c) { - if (c.error) { - throw new Error(c.error); - } - - console.log(a, b, c); - }); - }); -}); - -``` - -使用Generator函数可以大大简化上面的代码。 - -```javascript - -function* g(){ - try { - var a = yield foo('a'); - var b = yield foo('b'); - var c = yield foo('c'); - } catch (e) { - console.log(e); - } - - console.log(a, b, c); - -} - -``` - -## yield*语句 - -如果yield命令后面跟的是一个遍历器,需要在yield命令后面加上星号,表明它返回的是一个遍历器。这被称为yield*语句。 - -```javascript - -let delegatedIterator = (function* () { - yield 'Hello!'; - yield 'Bye!'; -}()); - -let delegatingIterator = (function* () { - yield 'Greetings!'; - yield* delegatedIterator; - yield 'Ok, bye.'; -}()); - -for(let value of delegatingIterator) { - console.log(value); -} -// "Greetings! -// "Hello!" -// "Bye!" -// "Ok, bye." - -``` - -上面代码中,delegatingIterator是代理者,delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Genertor函数,有递归的效果。 - -下面是一个稍微复杂的例子,使用yield*语句遍历完全二叉树。 - -```javascript - -// 下面是二叉树的构造函数, -// 三个参数分别是左树、当前节点和右树 -function Tree(left, label, right) { - this.left = left; - this.label = label; - this.right = right; -} - -// 下面是中序(inorder)遍历函数。 -// 由于返回的是一个遍历器,所以要用generator函数。 -// 函数体内采用递归算法,所以左树和右树要用yield*遍历 -function* inorder(t) { - if (t) { - yield* inorder(t.left); - yield t.label; - yield* inorder(t.right); - } -} - -// 下面生成二叉树 -function make(array) { - // 判断是否为叶节点 - if (array.length == 1) return new Tree(null, array[0], null); - return new Tree(make(array[0]), array[1], make(array[2])); -} -let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]); - -// 遍历二叉树 -var result = []; -for (let node of inorder(tree)) { - result.push(node); -} - -result -// ['a', 'b', 'c', 'd', 'e', 'f', 'g'] - -``` - -## 作为对象属性的Generator函数 - -如果一个对象的属性是Generator函数,可以简写成下面的形式。 - -```javascript - -let obj = { - * myGeneratorMethod() { - ··· - } -}; - -``` - -它的完整形式如下,两者是等价的。 - -```javascript - -let obj = { - myGeneratorMethod: function* () { - ··· - } -}; - -``` diff --git a/docs/reference.md b/docs/reference.md index b3bfe2d55..6deea0faf 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -47,6 +47,7 @@ - Kyle Simpson, [ES6 Generators: Complete Series](http://davidwalsh.name/es6-generators): 由浅入深探讨Generator的系列文章,共四篇 - Gajus Kuizinas, [The Definitive Guide to the JavaScript Generators](http://gajus.com/blog/2/the-definetive-guide-to-the-javascript-generators): 对Generator的综合介绍 - Jan Krems, [Generators Are Like Arrays](https://gist.github.com/jkrems/04a2b34fb9893e4c2b5c): 讨论Generator可以被当作数据结构看待 +- Harold Cooper, [Coroutine Event Loops in Javascript](http://syzygy.st/javascript-coroutines/): Generator用于实现状态机 ## Promise对象 From 78cacd8b27855a3c35f4ddf5a7e7b7de184a2605 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 28 Dec 2014 14:41:16 +0800 Subject: [PATCH 0010/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/generator.md b/docs/generator.md index 0815c1a54..7ecdeeead 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -52,7 +52,7 @@ hw.next() 由于Generator函数返回的遍历器,只有调用next方法才会遍历下一个成员,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志,next方法遇到yield,就会暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性的值。当下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。如果没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,作为value属性的值,如果该函数没有return语句,则value属性的值为undefined。另一方面,由于yield后面的表达式,直到调用next方法时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。 -yield语句与return语句有点像,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。 +yield语句与return语句有点像,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说Generator生成了一系列的值,这也就是它的名称的来历(在英语中,generator这个词是“生成器”的意思)。 Generator函数可以不用yield语句,这时就变成了一个单纯的暂缓执行函数。 @@ -561,3 +561,13 @@ function doStuff() { ``` 上面的函数,可以用一模一样的for...of循环处理!两相一比较,就不难看出Generator使得数据或者操作,具备了类似数组的接口。 + +## Generator与协程 + +协程(coroutine)是一种程序运行的方式。传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。协程与其不同,多个函数可以并行执行,但是只有一个函数处于正在运行的状态,其他函数都处于暂停态(suspended),函数之间可以交换执行权。也就是说,一个函数执行到一半,可以暂停执行,将执行权交给另一个函数,等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的函数,就称为协程。 + +不难看出,协程适合用于多任务运行的环境。在这个意义上,它与线程很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他都处于暂停状态。此外,线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。 + +Generator函数是ECMAScript 6对协程的实现,但属于不完全实现,只做到了暂停执行和转移执行权,有一些特性没有实现,比如不支持所调用的函数之中的yield语句。 + +如果将Generator函数看作多任务运行的方式,存在多个进入点和退出点。那么,一方面,并发的多任务可以写成多个Generator函数;另一方面,继发的任务则可以按照发生顺序,写在一个Generator函数之中。 From 189741eeed527904c0f4ae96339edd6e56238b3e Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 28 Dec 2014 22:01:28 +0800 Subject: [PATCH 0011/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9iterator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/destructuring.md | 11 +++ docs/function.md | 25 +++++ docs/generator.md | 65 ++++++++++++- docs/iterator.md | 212 ++++++++++++++++++++++++++++++------------ docs/reference.md | 4 +- 5 files changed, 257 insertions(+), 60 deletions(-) diff --git a/docs/destructuring.md b/docs/destructuring.md index c7bb98420..36ad9e1bc 100644 --- a/docs/destructuring.md +++ b/docs/destructuring.md @@ -99,6 +99,17 @@ const [v1, v2, ..., vN ] = array; ``` +对于Set结构,也可以使用数组的解构赋值。 + +```javascript + +[a, b, c] = new Set(["a", "b", "c"]) +a // "a" + +``` + +事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的结构赋值。 + ## 对象的解构赋值 解构不仅可以用于数组,还可以用于对象。 diff --git a/docs/function.md b/docs/function.md index 6487adf8f..e78dbacf7 100644 --- a/docs/function.md +++ b/docs/function.md @@ -219,6 +219,31 @@ d ``` +扩展运算符还可以将字符串转为真正的数组。 + +```javascript + +[..."hello"] +// [ "h", "e", "l", "l", "o" ] + +``` + +扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符。 + +```javascript + +var go = function*(){ + yield 1; + yield 2; + yield 3; +}; + +[...go()] // [1, 2, 3] + +``` + +上面代码中,变量go是一个Generator函数,执行后返回的是一个遍历器,对这个遍历器执行扩展运算符,就会将内部遍历得到的值,转为一个数组。 + ## 箭头函数 ES6允许使用“箭头”(=>)定义函数。 diff --git a/docs/generator.md b/docs/generator.md index 7ecdeeead..22a40054f 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -268,12 +268,53 @@ for(let value of delegatingIterator) { // "Greetings! // "Hello!" // "Bye!" -// "Ok, bye." +// "Ok, bye." ``` 上面代码中,delegatingIterator是代理者,delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Genertor函数,有递归的效果。 +如果`yield*`后面跟着一个数组,就表示该数组会返回一个遍历器,因此就会遍历数组成员。 + +```javascript + +function* gen(){ + yield* ["a", "b", "c"]; +} + +gen().next() // { value:"a", done:false } + +``` + +上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器。 + +`yield*`命令可以很方便地取出嵌套数组的所有成员。 + +```javascript + +function* iterTree(tree) { + if (Array.isArray(tree)) { + for(let i=0; i < tree.length; i++) { + yield* iterTree(tree[i]); + } + } else { + yield tree; + } +} + +const tree = [ 'a', ['b', 'c'], ['d', 'e'] ]; + +for(let x of iterTree(tree)) { + console.log(x); +} +// a +// b +// c +// d +// e + +``` + 下面是一个稍微复杂的例子,使用yield*语句遍历完全二叉树。 ```javascript @@ -522,6 +563,26 @@ for (let [key, value] of iterEntries(myObj)) { 上述代码中,myObj是一个普通对象,通过iterEntries函数,就有了iterator接口。也就是说,可以在任意对象上部署next方法。 +下面是一个对数组部署Iterator接口的例子,尽管数组原生具有这个接口。 + +```javascript + +function* makeSimpleGenerator(array){ + var nextIndex = 0; + + while(nextIndex < array.length){ + yield array[nextIndex++]; + } +} + +var gen = makeSimpleGenerator(['yo', 'ya']); + +gen.next().value // 'yo' +gen.next().value // 'ya' +gen.next().done // true + +``` + ### (4)作为数据结构 Generator可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为Generator函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。 @@ -570,4 +631,4 @@ function doStuff() { Generator函数是ECMAScript 6对协程的实现,但属于不完全实现,只做到了暂停执行和转移执行权,有一些特性没有实现,比如不支持所调用的函数之中的yield语句。 -如果将Generator函数看作多任务运行的方式,存在多个进入点和退出点。那么,一方面,并发的多任务可以写成多个Generator函数;另一方面,继发的任务则可以按照发生顺序,写在一个Generator函数之中。 +如果将Generator函数看作多任务运行的方式,存在多个进入点和退出点。那么,一方面,并发的多任务可以写成多个Generator函数;另一方面,继发的任务则可以按照发生顺序,写在一个Generator函数之中,然后用一个任务管理函数执行(参考本节的“控制流管理”部分)。 diff --git a/docs/iterator.md b/docs/iterator.md index ab210261b..5fa15b160 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -2,47 +2,44 @@ ## Iterator(遍历器) -遍历器(Iterator)是一种协议,任何对象只要部署这个协议,就可以完成遍历操作。在ES6中,遍历操作特指for...of循环。 +遍历器(Iterator)是一种接口规格,任何对象只要部署这个接口,就可以完成遍历操作。它的作用有两个,一是为各种数据结构,提供一个统一的、简便的接口,二是使得对象的属性能够按某种次序排列。在ES6中,遍历操作特指for...of循环,即Iterator接口主要供for...of循环使用。 -它的作用主要有两个,一是为遍历对象的属性提供统一的接口,二是为使得对象的属性能够按次序排列。 - -ES6的遍历器协议规定,部署了next方法的对象,就具备了遍历器功能。next方法必须返回一个包含value和done两个属性的对象。其中,value属性是当前遍历位置的值,done属性是一个布尔值,表示遍历是否结束。 +遍历器提供了一个指针,指向当前对象的某个属性,使用next方法,就可以将指针移动到下一个属性。next方法返回一个包含value和done两个属性的对象。其中,value属性是当前遍历位置的值,done属性是一个布尔值,表示遍历是否结束。下面是一个模拟next方法返回值的例子。 ```javascript function makeIterator(array){ - var nextIndex = 0; - - return { - next: function(){ - return nextIndex < array.length ? - {value: array[nextIndex++], done: false} : - {value: undefined, done: true}; - } + var nextIndex = 0; + return { + next: function(){ + return nextIndex < array.length ? + {value: array[nextIndex++], done: false} : + {value: undefined, done: true}; } + } } var it = makeIterator(['a', 'b']); -it.next().value // 'a' -it.next().value // 'b' -it.next().done // true +it.next() // { value: "a", done: false } +it.next() // { value: "b", done: false } +it.next() // { value: undefined, done: true } ``` -上面代码定义了一个makeIterator函数,它的作用是返回一个遍历器对象,用来遍历参数数组。请特别注意,next返回值的构造。 +上面代码定义了一个makeIterator函数,它的作用是返回一个遍历器对象,用来遍历参数数组。next方法依次遍历数组的每个成员,请特别注意,next返回值的构造。 下面是一个无限运行的遍历器例子。 ```javascript function idMaker(){ - var index = 0; + var index = 0; - return { - next: function(){ - return {value: index++, done: false}; - } + return { + next: function(){ + return {value: index++, done: false}; } + } } var it = idMaker(); @@ -54,29 +51,135 @@ it.next().value // '2' ``` -在ES6中,数组、类似数组的对象、Set和Map结构,都原生具备iterator接口,可以被for...of循环遍历。 +上面的例子,只是为了说明next方法返回值的结构。Iterator接口返回的遍历器,原生具备next方法,不用自己部署。所以,真正需要部署的是Iterator接口,让其返回一个遍历器。在ES6中,有三类数据结构原生具备Iterator接口:数组、类似数组的对象、Set和Map结构。除此之外,其他数据结构(主要是对象)的Iterator接口都需要自己部署。 -## for...of循环 +下面就是如何部署Iterator接口。一个对象如果要有Iterator接口,必须部署一个@@iterator方法(原型链上的对象具有该方法也可),该方法部署在一个键名为`Symbol.iterator`的属性上,对应的键值是一个函数,该函数返回一个遍历器对象。 + +```javascript + +class MySpecialTree { + // ... + [Symbol.iterator]() { + // ... + return theIterator; + } +} + +``` + +上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`是一个表达式,返回一个Symbol对象的iterator属性,这是一个类型为Symbol的特殊值,所以要放在方括号内。这里要注意,@@iterator的键名是`Symbol.iterator`,该方法执行后,返回一个当前对象的遍历器。 + +《数组的扩展》一章中提到,ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器。 + +```javascript + +var arr = [1, 5, 7]; +var arrEntries = arr.entries(); + +arrEntries.toString() +// "[object Array Iterator]" + +arrEntries === arrEntries[Symbol.iterator]() +// true + +``` + +上面代码中,entries方法返回的是一个遍历器(iterator),本质上就是调用了`Symbol.iterator`方法。 + +字符串是一个类似数组的对象,因此也原生具有Iterator接口。 + +```javascript + +var someString = "hi"; +typeof someString[Symbol.iterator] +// "function" + +var iterator = someString[Symbol.iterator](); + +iterator.next() // { value: "h", done: false } +iterator.next() // { value: "i", done: false } +iterator.next() // { value: undefined, done: true } + +``` + +上面代码中,调用`Symbol.iterator`方法返回一个遍历器,在这个遍历器上可以调用next方法,实现对于字符串的遍历。 + +可以覆盖原生的`Symbol.iterator`方法,达到修改遍历器行为的目的。 + +```javascript -ES6中,一个对象只要部署了next方法,就被视为具有iterator接口,就可以用for...of循环遍历它的值。下面用上一节的idMaker函数生成的it遍历器作为例子。 +var str = new String("hi"); + +[...str] // ["h", "i"] + +str[Symbol.iterator] = function() { + return { + next: function() { + if (this._first) { + this._first = false; + return { value: "bye", done: false }; + } else { + return { done: true }; + } + }, + _first: true + }; +}; + +[...str] // ["bye"] +str // "hi" + +``` + +上面代码中,字符串str的`Symbol.iterator`方法被修改了,所以扩展运算符(...)返回的值变成了bye,而字符串本身还是hi。 + +部署`Symbol.iterator`方法的最简单实现,还是使用下一节要提到的Generator函数。 ```javascript -for (var n of it) { - if (n > 5) - break; - console.log(n); +var myIterable = {}; + +myIterable[Symbol.iterator] = function* () { + yield 1; + yield 2; + yield 3; +}; +[...myIterable] // [1, 2, 3] + +// 或者采用下面的简洁写法 + +let obj = { + * [Symbol.iterator]() { + yield 'hello'; + yield 'world'; + } +}; + +for (let x of obj) { + console.log(x); } -// 0 -// 1 -// 2 -// 3 -// 4 -// 5 +// hello +// world + +``` + +如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。 + +```javascript + +var obj = {}; + +obj[Symbol.iterator] = () => 1; + +[...obj] // TypeError: [] is not a function ``` -上面代码说明,for...of默认从0开始循环。 +上面代码中,变量obj的@@iterator方法返回的不是遍历器,因此报错。 + +## for...of循环 + +ES6中,一个对象只要部署了@@iterator方法,就被视为具有iterator接口,就可以用for...of循环遍历它的值。也就是说,for...of循环内部调用是原对象的`Symbol.iterator`方法。 数组原生具备iterator接口。 @@ -85,15 +188,23 @@ for (var n of it) { const arr = ['red', 'green', 'blue']; for(let v of arr) { - console.log(v); + console.log(v); // red green blue } -// red -// green -// blue ``` -for...of循环完全可以取代数组实例的forEach方法。 +上面代码说明,for...of循环可以代替数组实例的forEach方法。 + +```javascript + +const arr = ['red', 'green', 'blue']; + +arr.forEach(function (element, index) { + console.log(element); // red green blue + console.log(index); // 0 1 2 +}); + +``` JavaScript原有的for...in循环,只能获得对象的键名,不能直接获取键值。ES6提供for...of循环,允许遍历获得键值。 @@ -101,26 +212,18 @@ JavaScript原有的for...in循环,只能获得对象的键名,不能直接 var arr = ["a", "b", "c", "d"]; for (a in arr) { - console.log(a); + console.log(a); // 0 1 2 3 } -// 0 -// 1 -// 2 -// 3 for (a of arr) { - console.log(a); + console.log(a); // a b c d } -// a -// b -// c -// d ``` 上面代码表明,for...in循环读取键名,for...of循环读取键值。如果要通过for...of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法,参见《数组的扩展》章节。 -对于Set和Map结构的数据,可以直接使用for...of循环。 +Set和Map结构也原生具有Iterator接口,可以直接使用for...of循环。 ```javascript @@ -182,13 +285,8 @@ for (e of es6) { let str = "hello"; for (let s of str) { - console.log(s); + console.log(s); // h e l l o } -// h -// e -// l -// l -// o // DOM NodeList对象的例子 diff --git a/docs/reference.md b/docs/reference.md index 6deea0faf..1f61d8bab 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -35,9 +35,10 @@ - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 -## Generator +## Iterator和Generator - Mozilla Developer Network, [Iterators and generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) +- Mozilla Developer Network, [The Iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/The_Iterator_protocol) - Matt Baker, [Replacing callbacks with ES6 Generators](http://flippinawesome.org/2014/02/10/replacing-callbacks-with-es6-generators/) - Steven Sanderson, [Experiments with Koa and JavaScript Generators](http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/) - jmar777, [What's the Big Deal with Generators?](http://devsmash.com/blog/whats-the-big-deal-with-generators) @@ -63,6 +64,7 @@ - Jack Franklin, [JavaScript Modules the ES6 Way](http://24ways.org/2014/javascript-modules-the-es6-way/): ES6模块入门 - Axel Rauschmayer, [ECMAScript 6 modules: the final syntax](http://www.2ality.com/2014/09/es6-modules-final.html): ES6模块的介绍,以及与CommonJS规格的详细比较 - Dave Herman, [Static module resolution](http://calculist.org/blog/2012/06/29/static-module-resolution/): ES6模块的静态化设计思想 +- Axel Rauschmayer, [ECMAScript 6: new OOP features besides classes](http://www.2ality.com/2014/12/es6-oop.html) ## 工具 From 38adc8e8ebb140b2cbb9523f021000f8cfc8e471 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 28 Dec 2014 23:34:33 +0800 Subject: [PATCH 0012/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9symbol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 48 +++++++++++++++++++++++--- docs/iterator.md | 28 ++++++++++++++- docs/object.md | 87 ++++++++++++++++++++++++++++++++++++++++++----- docs/reference.md | 1 + 4 files changed, 149 insertions(+), 15 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index 22a40054f..d913c8176 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -487,15 +487,53 @@ function* numbers() { ### (2)控制流管理 -总结一下,如果某个操作非常耗时,可以把它拆成N步。 +如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。 + +```javascript + +step1(function (value1) { + step2(value1, function(value2) { + step3(value2, function(value3) { + step4(value3, function(value4) { + // Do something with value4 + }); + }); + }); +}); + +``` + +采用Promise改写上面的代码。 + +```javascript + +Q.fcall(step1) +.then(step2) +.then(step3) +.then(step4) +.then(function (value4) { + // Do something with value4 +}, function (error) { + // Handle any error from step1 through step4 +}) +.done(); + +``` + +上面代码已经把回调函数,改成了直线执行的形式。Generator函数可以进一步改善代码运行流程。 ```javascript function* longRunningTask() { - yield step1(); - yield step2(); - // ... - yield stepN(); + try { + var value1 = yield step1(); + var value2 = yield step2(value1); + var value3 = yield step3(value2); + var value4 = yield step4(value3); + // Do something with value4 + } catch (e) { + // Handle any error from step1 through step4 + } } ``` diff --git a/docs/iterator.md b/docs/iterator.md index 5fa15b160..d63d61c69 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -67,7 +67,33 @@ class MySpecialTree { ``` -上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`是一个表达式,返回一个Symbol对象的iterator属性,这是一个类型为Symbol的特殊值,所以要放在方括号内。这里要注意,@@iterator的键名是`Symbol.iterator`,该方法执行后,返回一个当前对象的遍历器。 +上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一节)。这里要注意,@@iterator的键名是`Symbol.iterator`,键值是一个方法(函数),该方法执行后,返回一个当前对象的遍历器。 + +下面是为对象添加Iterator接口的例子。 + +```javascript + +let obj = { + data: [ 'hello', 'world' ], + [Symbol.iterator]() { + const self = this; + let index = 0; + return { + next() { + if (index < self.data.length) { + return { + value: self.data[index++], + done: false + }; + } else { + return { value: undefined, done: true }; + } + } + }; + } +}; + +``` 《数组的扩展》一章中提到,ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器。 diff --git a/docs/object.md b/docs/object.md index 60986c8cd..8a030e6dc 100644 --- a/docs/object.md +++ b/docs/object.md @@ -305,7 +305,20 @@ Object.getPrototypeOf(obj) ## Symbol -ES6引入了一种新的原始数据类型Symbol。它通过Symbol函数生成。 +ES6引入了一种新的原始数据类型Symbol,表示独一无二的ID。它通过Symbol函数生成。 + +```javascript + +let symbol1 = Symbol(); + +typeof symbol +// "symbol" + +``` + +上面代码中,变量symbol1就是一个独一无二的ID。typeof运算符的结果,表明变量symbol1是Symbol数据类型,而不是字符串之类的其他类型。 + +Symbol函数可以接受一个字符串作为参数,表示Symbol实例的名称。 ```javascript @@ -314,15 +327,34 @@ var mySymbol = Symbol('Test'); mySymbol.name // Test -typeof mySymbol -// "symbol" - ``` -上面代码表示,Symbol函数接受一个字符串作为参数,用来指定生成的Symbol的名称,可以通过name属性读取。typeof运算符的结果,表明Symbol是一种单独的数据类型。 +上面代码表示,Symbol函数的字符串参数,用来指定生成的Symbol的名称,可以通过name属性读取。之所以要新增name属性,是因为键名是Symbol类型,而有些场合需要一个字符串类型的值来指代这个键。 注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。 +Symbol类型的值不能与其他类型的值进行运算,会报错。 + +```javascript + +var sym = Symbol('My symbol'); +'' + sym +// TypeError: Cannot convert a Symbol value to a string + +``` + +但是,Symbol类型的值可以转为字符串。 + +```javascript + +String(sym) +// 'Symbol(My symbol)' + +sym.toString() +// 'Symbol(My symbol)' + +``` + symbol的最大特点,就是每一个Symbol都是不相等的,保证产生一个独一无二的值。 ```javascript @@ -331,6 +363,10 @@ let w1 = Symbol(); let w2 = Symbol(); let w3 = Symbol(); +w1 === w2 // false +w1 === w3 // false +w2 === w3 // false + function f(w) { switch (w) { case w1: @@ -346,18 +382,27 @@ function f(w) { 上面代码中,w1、w2、w3三个变量都等于`Symbol()`,但是它们的值是不相等的。 -由于这种特点,Symbol类型适合作为标识符,用于对象的属性名,保证了属性名之间不会发生冲突。如果一个对象由多个模块构成,这样就不会出现同名的属性。另一种用途是,可以防止属性值被不小心修改。 +由于这种特点,Symbol类型适合作为标识符,用于对象的属性名,保证了属性名之间不会发生冲突。如果一个对象由多个模块构成,这样就不会出现同名的属性,也就防止了键值被不小心改写或覆盖。Symbol类型还可以用于定义一组常量,防止它们的值发生冲突。 ```javascript -var a = {}; var mySymbol = Symbol(); +// 第一种写法 +var a = {}; a[mySymbol] = 'Hello!'; -//另一种写法 +// 第二种写法 +var a = { + [mySymbol]: 123 +}; + +// 第三种写法 +var a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); +a[mySymbol] // "Hello!" + ``` 上面代码通过方括号结构和Object.defineProperty两种方法,将对象的属性名指定为一个Symbol值。 @@ -387,6 +432,15 @@ a.size // 1 ``` +为Symbol函数添加一个参数,就可以引用了。 + +```javascript + +let a = Map(); +a.set(Symbol('my_key'), 'Noise'); + +``` + 如果要在对象内部使用Symbol属性名,必须采用属性名表达式。 ```javascript @@ -411,7 +465,7 @@ let obj = { ``` -Symbol类型作为属性名,不会出现在for...in循环中,也不会被Object.getOwnPropertyNames方法返回,但是有一个对应的Object.getOwnPropertySymbols方法,以及Object.getOwnPropertyKeys方法都可以获取Symbol属性名。 +Symbol类型作为属性名,不会出现在for...in循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回,但是有一个对应的Object.getOwnPropertySymbols方法,以及Object.getOwnPropertyKeys方法都可以获取Symbol属性名。 ```javascript @@ -433,6 +487,21 @@ Object.getOwnPropertySymbols(obj) 上面代码中,使用Object.getOwnPropertyNames方法得不到Symbol属性名,需要使用Object.getOwnPropertySymbols方法。 +Reflect.ownKeys方法返回所有类型的键名。 + +```javascript + +let obj = { + [Symbol('my_key')]: 1, + enum: 2, + nonEnum: 3 +}; + +Reflect.ownKeys(obj) +// [Symbol(my_key), 'enum', 'nonEnum'] + +``` + ## Proxy 所谓Proxy,可以理解成在目标对象之前,架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 diff --git a/docs/reference.md b/docs/reference.md index 1f61d8bab..d8402bb59 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -34,6 +34,7 @@ - Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 +- Axel Rauschmayer, [Symbols in ECMAScript 6](http://www.2ality.com/2014/12/es6-symbols.html): Symbol简介 ## Iterator和Generator From 2556bf89627fa823c7437d4951327d4a79f025d2 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 30 Dec 2014 09:29:50 +0800 Subject: [PATCH 0013/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9Readme=E5=92=8Cgene?= =?UTF-8?q?rator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- docs/generator.md | 37 +++++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0fe044b2a..1c34d93b8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ 本书为中级难度,适合已有一定JavaScript语言基础的读者,用来了解这门语言的最新发展;也可当作参考手册,查寻新增的语法点。 -全书已由电子工业出版社出版([版权页](images/copyright.png),[内页1](images/page1.png),[内页2](images/page2.png)),铜版纸全彩印刷,附有索引。电子版与纸版的内容是一致的,如果您对本书感兴趣,建议考虑购买纸版。这样可以使出版社不因出版开源书籍而亏钱,进而鼓励更多的作者开源自己的书籍。 +全书已由电子工业出版社出版([版权页](images/copyright.png),[内页1](images/page1.png),[内页2](images/page2.png)),铜版纸全彩印刷,附有索引。纸版是根据电子版排印的,内容截止到2014年10月,感谢张春雨编辑支持我将全书开源的做法。如果您对本书感兴趣,建议考虑购买纸版。这样可以使出版社不因出版开源书籍而亏钱,进而鼓励更多的作者开源自己的书籍。 - [京东](http://item.jd.com/11526272.html) - [当当](http://product.dangdang.com/23546442.html) diff --git a/docs/generator.md b/docs/generator.md index d913c8176..a560a3e79 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -1,6 +1,8 @@ # Generator 函数 -## 含义 +## 语法 + +### 简介 所谓Generator,有多种理解角度。首先,可以把它理解成一个内部状态的遍历器,每调用一次,内部状态发生一次改变(可以理解成发生某些事件)。ES6引入Generator函数,作用就是可以完全控制内部状态的变化,依次遍历这些状态。 @@ -72,7 +74,7 @@ setTimeout(function () { 上面代码中,函数f如果是普通函数,在为变量generator赋值时就会执行。但是,函数f是一个Generator函数,就变成只有调用next方法时,函数f才会执行。 -## next方法的参数 +### next方法的参数 yield语句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。 @@ -120,7 +122,7 @@ it.next(13) 注意,由于next方法的参数表示上一个yield语句的返回值,所以第一次使用next方法时,不能带有参数。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。 -## for...of循环 +### for...of循环 for...of循环可以自动遍历Generator函数,且此时不再需要调用next方法。 @@ -165,7 +167,7 @@ for (let n of fibonacci()) { 从上面代码可见,使用for...of语句时不需要使用next方法。 -## throw方法 +### throw方法 Generator函数还有一个特点,它可以在函数体外抛出错误,然后在函数体内捕获。 @@ -245,7 +247,7 @@ function* g(){ ``` -## yield*语句 +### yield*语句 如果yield命令后面跟的是一个遍历器,需要在yield命令后面加上星号,表明它返回的是一个遍历器。这被称为yield*语句。 @@ -357,7 +359,7 @@ result ``` -## 作为对象属性的Generator函数 +### 作为对象属性的Generator函数 如果一个对象的属性是Generator函数,可以简写成下面的形式。 @@ -383,7 +385,9 @@ let obj = { ``` -## Generator与状态机 +## 含义 + +### Generator与状态机 Generator是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。 @@ -417,6 +421,16 @@ var clock = function*(_) { 上面的Generator实现与ES5实现对比,可以看到少了用来保存状态的外部变量ticking,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。 +### Generator与协程 + +协程(coroutine)是一种程序运行的方式。传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。协程与其不同,多个函数可以并行执行,但是只有一个函数处于正在运行的状态,其他函数都处于暂停态(suspended),函数之间可以交换执行权。也就是说,一个函数执行到一半,可以暂停执行,将执行权交给另一个函数,等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的函数,就称为协程。 + +不难看出,协程适合用于多任务运行的环境。在这个意义上,它与线程很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他都处于暂停状态。此外,线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。 + +Generator函数是ECMAScript 6对协程的实现,但属于不完全实现,只做到了暂停执行和转移执行权,有一些特性没有实现,比如不支持所调用的函数之中的yield语句。 + +如果将Generator函数看作多任务运行的方式,存在多个进入点和退出点。那么,一方面,并发的多任务可以写成多个Generator函数;另一方面,继发的任务则可以按照发生顺序,写在一个Generator函数之中,然后用一个任务管理函数执行(参考本节的“控制流管理”部分)。 + ## 应用 Generator可以暂停函数执行,返回任意表达式的值。这种特点使得Generator有多种应用场景。 @@ -661,12 +675,3 @@ function doStuff() { 上面的函数,可以用一模一样的for...of循环处理!两相一比较,就不难看出Generator使得数据或者操作,具备了类似数组的接口。 -## Generator与协程 - -协程(coroutine)是一种程序运行的方式。传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。协程与其不同,多个函数可以并行执行,但是只有一个函数处于正在运行的状态,其他函数都处于暂停态(suspended),函数之间可以交换执行权。也就是说,一个函数执行到一半,可以暂停执行,将执行权交给另一个函数,等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的函数,就称为协程。 - -不难看出,协程适合用于多任务运行的环境。在这个意义上,它与线程很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他都处于暂停状态。此外,线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。 - -Generator函数是ECMAScript 6对协程的实现,但属于不完全实现,只做到了暂停执行和转移执行权,有一些特性没有实现,比如不支持所调用的函数之中的yield语句。 - -如果将Generator函数看作多任务运行的方式,存在多个进入点和退出点。那么,一方面,并发的多任务可以写成多个Generator函数;另一方面,继发的任务则可以按照发生顺序,写在一个Generator函数之中,然后用一个任务管理函数执行(参考本节的“控制流管理”部分)。 From fa535283126ef0cf11f54095b8c50aa59dc7dbc9 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 30 Dec 2014 14:01:25 +0800 Subject: [PATCH 0014/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9object/proxy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/object.md | 116 +++++++++++++++++++++++++++++++++++++++------- docs/reference.md | 10 ++-- 2 files changed, 107 insertions(+), 19 deletions(-) diff --git a/docs/object.md b/docs/object.md index 8a030e6dc..11408ceca 100644 --- a/docs/object.md +++ b/docs/object.md @@ -504,16 +504,18 @@ Reflect.ownKeys(obj) ## Proxy -所谓Proxy,可以理解成在目标对象之前,架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 +Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。 -ES6原生提供Proxy构造函数,用来生成proxy实例对象。 +Proxy可以理解成在目标对象之前,架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作。 + +ES6原生提供Proxy构造函数,用来生成Proxy实例。 ```javascript var proxy = new Proxy({}, { - get: function(target, property) { - return 35; - } + get: function(target, property) { + return 35; + } }); proxy.time // 35 @@ -522,24 +524,46 @@ proxy.title // 35 ``` -上面代码就是Proxy构造函数使用实例,它接受两个参数,第一个是所要代理的目标对象(上例是一个空对象),第二个是拦截函数,它有一个get方法,用来拦截对目标对象的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。 +作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个设置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,设置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。 + +注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。 + +Proxy实例也可以作为其他对象的原型对象。 + +```javascript + +var proxy = new Proxy({}, { + get: function(target, property) { + return 35; + } +}); + +let obj = Object.create(proxy); + +obj.time // 35 + +``` + +上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所有根据原型链,会在proxy对象上读取该属性,导致被拦截。 + +对于没有设置拦截的操作,则直接落在目标函数上,按照原先的方式产生结果。 -下面是另一个拦截函数的例子。 +下面是另一个拦截读取操作的例子。 ```javascript var person = { - name: "张三" + name: "张三" }; var proxy = new Proxy(person, { - get: function(target, property) { - if (property in target) { - return target[property]; - } else { - throw new ReferenceError("Property \"" + property + "\" does not exist."); - } - } + get: function(target, property) { + if (property in target) { + return target[property]; + } else { + throw new ReferenceError("Property \"" + property + "\" does not exist."); + } + } }); proxy.name // "张三" @@ -579,7 +603,67 @@ person.age = 300 // 报错 ``` -上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误。 +上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新DOM。 + +ownKeys方法用来拦截Object.keys()操作。 + +```javascript + +let target = {}; + +let handler = { + ownKeys(target) { + return ['hello', 'world']; + } +}; + +let proxy = new Proxy(target, handler); + +Object.keys(proxy) +// [ 'hello', 'world' ] + +``` + +上面代码拦截了对于target对象的Object.keys()操作,返回预先设定的数组。 + +Proxy支持的拦截操作一览。 + +- defineProperty(target, propKey, propDesc):返回一个布尔值,拦截Object.defineProperty(proxy, propKey, propDesc) +- deleteProperty(target, propKey) :返回一个布尔值,拦截delete proxy[propKey] +- enumerate(target):返回一个遍历器,拦截for (x in proxy) +- get(target, propKey, receiver):返回类型不限,拦截对象属性的读取 +- getOwnPropertyDescriptor(target, propKey) :返回属性的描述对象,拦截Object.getOwnPropertyDescriptor(proxy, propKey) +- getPrototypeOf(target) :返回一个对象,拦截Object.getPrototypeOf(proxy) +- has(target, propKey):返回一个布尔值,拦截propKey in proxy +- isExtensible(target):返回一个布尔值,拦截Object.isExtensible(proxy) +- ownKeys(target):返回一个数组,拦截Object.getOwnPropertyPropertyNames(proxy)、Object.getOwnPropertyPropertySymbols(proxy)、Object.keys(proxy) +- preventExtensions(target):返回一个布尔值,拦截Object.preventExtensions(proxy) +- set(target, propKey, value, receiver):返回一个布尔值,拦截对象属性的设置 +- setPrototypeOf(target, proto):返回一个布尔值,拦截Object.setPrototypeOf(proxy, proto) + +如果目标对象是函数,那么还有两种额外操作可以拦截。 + +- apply方法:拦截Proxy实例作为函数调用的操作,比如proxy(···)、proxy.call(···)、proxy.apply(···)。 +- construct方法:拦截Proxy实例作为构造函数调用的操作,比如new proxy(···)。 + +Proxy.revocable方法返回一个可取消的Proxy实例。 + +```javascript + +let target = {}; +let handler = {}; + +let {proxy, revoke} = Proxy.revocable(target, handler); + +proxy.foo = 123; +proxy.foo // 123 + +revoke(); +proxy.foo // TypeError: Revoked + +``` + +Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。 ## Object.observe(),Object.unobserve() diff --git a/docs/reference.md b/docs/reference.md index d8402bb59..d0fec441d 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -28,13 +28,17 @@ - Jack Franklin, [Real Life ES6 - Arrow Functions](http://javascriptplayground.com/blog/2014/04/real-life-es6-arrow-fn/) - Axel Rauschmayer, [Handling required parameters in ECMAScript 6](http://www.2ality.com/2014/04/required-parameters-es6.html) - Axel Rauschmayer, [ECMAScript 6’s new array methods](http://www.2ality.com/2014/05/es6-array-methods.html): 对ES6新增的数组方法的全面介绍 -- Nicholas C. Zakas, [Creating defensive objects with ES6 proxies](http://www.nczonline.net/blog/2014/04/22/creating-defensive-objects-with-es6-proxies/) -- Addy Osmani, [Data-binding Revolutions with Object.observe()](http://www.html5rocks.com/en/tutorials/es7/observe/): 介绍Object.observe()的概念 -- Sella Rafaeli, [Native JavaScript Data-Binding](http://www.sellarafaeli.com/blog/native_javascript_data_binding): 如何使用Object.observe方法,实现数据对象与DOM对象的双向绑定 - Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 + +## Object + +- Nicholas C. Zakas, [Creating defensive objects with ES6 proxies](http://www.nczonline.net/blog/2014/04/22/creating-defensive-objects-with-es6-proxies/) +- Addy Osmani, [Data-binding Revolutions with Object.observe()](http://www.html5rocks.com/en/tutorials/es7/observe/): 介绍Object.observe()的概念 +- Sella Rafaeli, [Native JavaScript Data-Binding](http://www.sellarafaeli.com/blog/native_javascript_data_binding): 如何使用Object.observe方法,实现数据对象与DOM对象的双向绑定 - Axel Rauschmayer, [Symbols in ECMAScript 6](http://www.2ality.com/2014/12/es6-symbols.html): Symbol简介 +- Axel Rauschmayer, [Meta programming with ECMAScript 6 proxies](http://www.2ality.com/2014/12/es6-proxies.html): Proxy详解 ## Iterator和Generator From a7a7fdaa62e36e02e5d12bad89ccbf9e661e1a2d Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 4 Jan 2015 10:57:26 +0800 Subject: [PATCH 0015/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index a560a3e79..5ecb57758 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -4,7 +4,7 @@ ### 简介 -所谓Generator,有多种理解角度。首先,可以把它理解成一个内部状态的遍历器,每调用一次,内部状态发生一次改变(可以理解成发生某些事件)。ES6引入Generator函数,作用就是可以完全控制内部状态的变化,依次遍历这些状态。 +所谓Generator,有多种理解角度。首先,可以把它理解成一个函数的内部状态的遍历器,每调用一次,函数的内部状态发生一次改变(可以理解成发生某些事件)。ES6引入Generator函数,作用就是可以完全控制函数的内部状态的变化,依次遍历这些状态。 在形式上,Generator是一个普通函数,但是有两个特征。一是,function命令与函数名之间有一个星号;二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态(yield语句在英语里的意思就是“产出”)。 @@ -97,6 +97,8 @@ g.next(true) // { value: 0, done: false } 上面代码先定义了一个可以无限运行的Generator函数f,如果next方法没有参数,每次运行到yield语句,变量reset的值总是undefined。当next方法带一个参数true时,当前的变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。 +这个功能有很重要的语法意义。Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。 + 再看一个例子。 ```javascript @@ -425,9 +427,9 @@ var clock = function*(_) { 协程(coroutine)是一种程序运行的方式。传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。协程与其不同,多个函数可以并行执行,但是只有一个函数处于正在运行的状态,其他函数都处于暂停态(suspended),函数之间可以交换执行权。也就是说,一个函数执行到一半,可以暂停执行,将执行权交给另一个函数,等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的函数,就称为协程。 -不难看出,协程适合用于多任务运行的环境。在这个意义上,它与线程很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他都处于暂停状态。此外,线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。 +不难看出,协程适合用于多任务运行的环境。在这个意义上,它与线程很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他都处于暂停状态。此外,线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。从实现上看,在内存中,子例程只使用一个栈(stack),而协程是同时存在多个栈,但只有一个栈是在运行状态,也就是说,协程是以多占用内存为代价,实现多任务的并行。 -Generator函数是ECMAScript 6对协程的实现,但属于不完全实现,只做到了暂停执行和转移执行权,有一些特性没有实现,比如不支持所调用的函数之中的yield语句。 +Generator函数是ECMAScript 6对协程的实现,但属于不完全实现,只做到了暂停执行和转移执行权,有一些特性没有实现,比如不支持所调用的函数之中的yield语句(即递归执行yield语句)。 如果将Generator函数看作多任务运行的方式,存在多个进入点和退出点。那么,一方面,并发的多任务可以写成多个Generator函数;另一方面,继发的任务则可以按照发生顺序,写在一个Generator函数之中,然后用一个任务管理函数执行(参考本节的“控制流管理”部分)。 @@ -560,7 +562,7 @@ scheduler(longRunningTask()); function scheduler(task) { setTimeout(function () { - if (!task.next().done) { + if (!task.next(task.value).done) { scheduler(task); } }, 0); From efee4f880573c46980e69d639bea3af7f5d341c3 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 6 Jan 2015 16:23:11 +0800 Subject: [PATCH 0016/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9set-map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/function.md | 18 ++- docs/reference.md | 1 + docs/set-map.md | 345 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 328 insertions(+), 36 deletions(-) diff --git a/docs/function.md b/docs/function.md index e78dbacf7..e3e8feeda 100644 --- a/docs/function.md +++ b/docs/function.md @@ -228,7 +228,23 @@ d ``` -扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符。 +扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。 + +```javascript + +let map = new Map([ + [1, 'one'], + [2, 'two'], + [3, 'three'], +]); + + +let arr = [...map.keys()]; // [1, 2, 3] + +``` + +Generator函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。 + ```javascript diff --git a/docs/reference.md b/docs/reference.md index d0fec441d..4e2fcf0da 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -30,6 +30,7 @@ - Axel Rauschmayer, [ECMAScript 6’s new array methods](http://www.2ality.com/2014/05/es6-array-methods.html): 对ES6新增的数组方法的全面介绍 - Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 +- Axel Rauschmayer, [ECMAScript 6: maps and sets](http://www.2ality.com/2015/01/es6-maps-sets.html): Set和Map结构的详细介绍 - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 ## Object diff --git a/docs/set-map.md b/docs/set-map.md index 746b23053..50b55494f 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -2,6 +2,8 @@ ## Set +### 基本用法 + ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 Set本身是一个构造函数,用来生成Set数据结构。 @@ -19,7 +21,7 @@ for (i of s) {console.log(i)} 上面代码通过add方法向Set结构加入成员,结果表明Set结构不会添加重复的值。 -Set函数接受一个数组作为参数,用来初始化。 +Set函数可以接受一个数组作为参数,用来初始化。 ```javascript @@ -30,7 +32,23 @@ items.size ``` -向Set加入值的时候,不会发生类型转换。这意味在Set中,5和“5”是两个不同的值。 +向Set加入值的时候,不会发生类型转换,5和“5”是两个不同的值。Set内部判断两个值是否精确相等,使用的是精确相等运算符(===)。这意味着,两个对象总是不相等的。 + +```javascript + +let set = new Set(); + +set.add({}) +set.size // 1 + +set.add({}) +set.size // 2 + +``` + +上面代码表示,由于两个空对象不是精确相等,所以它们被视为两个值。 + +### 属性和方法 Set结构有以下属性。 @@ -39,10 +57,10 @@ Set结构有以下属性。 Set数据结构有以下方法。 -- add(value):添加某个值。 -- delete(value):删除某个值。 -- has(value):返回一个布尔值,表示该值是否为set的成员。 -- clear():清除所有成员。 +- add(value):添加某个值,返回Set结构本身。 +- delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 +- has(value):返回一个布尔值,表示该值是否为Set的成员。 +- clear():清除所有成员,没有返回值。 下面是这些属性和方法的使用演示。 @@ -109,7 +127,113 @@ function dedupe(array) { ``` -Set的遍历,可以借助for...of循环完成,参见《Iterator和for...of循环》章节。 +### 遍历操作 + +Set结构有一个values方法,返回一个遍历器。 + +```javascript + +let set = new Set(['red', 'green', 'blue']); +for ( let item of set.values() ){ + console.log(item); +} +// red +// green +// blue + +``` + +Set结构的默认遍历器就是它的values方法。 + +```javascript + +Set.prototype[Symbol.iterator] === Set.prototype.values +// true + +``` + +这意味着,可以省略values方法,直接用for...of循环遍历Set。 + +```javascript + +let set = new Set(['red', 'green', 'blue']); + +for (let x of set) { + console.log(x); +} +// red +// green +// blue + +``` + +Set结构的foreach方法,用于对每个成员执行某种操作,返回修改后的Set结构。 + +```javascript + +let set = new Set([1, 2, 3]); + +set.foreach((value, key) => value*2 ) +// 返回Set结构{2, 4, 6} + +``` + +上面代码说明,foreach方法的参数就是一个处理函数。该函数的参数依次为键值、键名、集合本身(上例省略了该参数)。另外,foreach方法还可以有第二个参数,表示绑定的this对象。 + +为了与Map结构保持一致,Set结构也有keys和entries方法,这时每个值的键名就是键值。 + +```javascript + +let set = new Set(['red', 'green', 'blue']); +for ( let item of set.keys() ){ + console.log(item); +} +// red +// green +// blue + +for ( let [key, value] of set.entries() ){ + console.log(key, value); +} +// red, red +// green, green +// blue, blue + +``` + +由于扩展运算符(...)内部使用for...of循环,所以也可以用于Set结构。 + +```javascript + +let set = new Set(['red', 'green', 'blue']); +let arr = [...set]; +// ['red', 'green', 'blue'] + +``` + +这就提供了另一种便捷的去除数组重复元素的方法。 + +```javascript + +let arr = [3, 5, 2, 2, 5, 5]; +let unique = [...new Set(arr)]; +// [3, 5, 2] + +``` + +而且,数组的map和filter方法也可以用于Set了。 + +```javascript + +let set = new Set([1, 2, 3]); +set = new Set([...set].map(x => x * 2)); +// 返回Set结构:{2, 4, 6} + +let set = new Set([1, 2, 3, 4, 5]); +set = new Set([...set].filter(x => (x % 2) == 0)); +// 返回Set结构:{2, 4} + +``` ## WeakSet @@ -137,10 +261,9 @@ var ws = new WeakSet(a); 上面代码中,a是一个数组,它有两个成员,也都是数组。将a作为WeakSet构造函数的参数,a的成员会自动成为WeakSet的成员。 -WeakSet结构有以下四个方法。 +WeakSet结构有以下三个方法。 - **WeakSet.prototype.add(value)**:向WeakSet实例添加一个新成员。 -- **WeakSet.prototype.clear()**:清除WeakSet实例的所有成员。 - **WeakSet.prototype.delete(value)**:清除WeakSet实例的指定成员。 - **WeakSet.prototype.has(value)**:返回一个布尔值,表示某个值是否在WeakSet实例之中。 @@ -182,24 +305,25 @@ data[element] = metadata; 上面代码原意是将一个DOM节点作为对象data的键,但是由于对象只接受字符串作为键名,所以element被自动转为字符串`[Object HTMLDivElement]`。 -为了解决这个问题,ES6提供了map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,对象也可以当作键。 +为了解决这个问题,ES6提供了map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应。 ```javascript var m = new Map(); - -o = {p: "Hello World"}; +var o = {p: "Hello World"}; m.set(o, "content") +m.get(o) // "content" -console.log(m.get(o)) -// "content" +m.has(o) // true +m.delete(o) // true +m.has(o) // false ``` -上面代码使用set方法,将对象o当作m的一个键,然后又使用get方法读取这个键。 +上面代码使用set方法,将对象o当作m的一个键,然后又使用get方法读取这个键,接着使用delete方法删除了这个键。 -map函数也可以接受一个数组进行初始化,该数组的成员是一个个表示键值对的数组。 +作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。 ```javascript @@ -245,20 +369,54 @@ map.get(k2) // 222 上面代码中,变量k1和k2的值是一样的,但是它们在Map结构中被视为两个键。 -由上可知,map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。 +由上可知,Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。 + +如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map将其视为一个键。这对于NaN,以及+0和-0都成立。 + +```javascript + +let map = new Map(); + +map.set(NaN, 123); +map.get(NaN) // 123 + +map.set(-0, 123); +map.get(+0) // 123 + +``` + +如果读取一个未知的键,则返回undefined。 + +```javascript + +new Map().get('asfddfsasadf') +// undefined + +``` **(2)属性和方法** Map数据结构有以下属性和方法。 - size:返回成员总数。 -- set(key, value):设置一个键值对。 -- get(key):读取一个键。 +- set(key, value):设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。 +- get(key):读取key对应的键值,如果找不到key,返回undefined。 - has(key):返回一个布尔值,表示某个键是否在Map数据结构中。 -- delete(key):删除某个键。 -- clear():清除所有成员。 +- delete(key):删除某个键,返回true。如果删除失败,返回false。 +- clear():清除所有成员,没有返回值。 -下面是一些用法实例。 +set()方法返回的是Map本身,因此可以采用链式写法。 + +```javascript + +let map = new Map() + .set(1, 'a') + .set(2, 'b') + .set(3, 'c'); + +``` + +下面是has()和delete()的例子。 ```javascript @@ -285,6 +443,20 @@ m.get("edition") // 6 ``` +下面是size属性和clear方法的例子。 + +```javascript + +let map = new Map(); +map.set('foo', true); +map.set('bar', false); + +map.size // 2 +map.clear() +map.size // 0 + +``` + **(3)遍历** Map原生提供三个遍历器。 @@ -297,31 +469,101 @@ Map原生提供三个遍历器。 ```javascript +let map = new Map([ + ['F', 'no'], + ['T', 'yes'], +]); + for (let key of map.keys()) { - console.log("Key: %s", key); + console.log(key); } +// "F" +// "T" for (let value of map.values()) { - console.log("Value: %s", value); + console.log(value); } +// "no" +// "yes" for (let item of map.entries()) { - console.log("Key: %s, Value: %s", item[0], item[1]); + console.log(item[0], item[1]); +} +// "F" "no" +// "T" "yes" + +// 或者 +for (let [key, value] of map.entries()) { + console.log(key, value); } -// same as using map.entries() -for (let item of map) { - console.log("Key: %s, Value: %s", item[0], item[1]); +// 等同于使用map.entries() +for (let [key, value] of map) { + console.log(key, value); } ``` +上面代码最后的那个例子,表示Map结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。 + +```javascript + +map[Symbol.iterator] === map.entries +// true + +``` + +Map结构转为数组结构,比较快速的方法是结合使用扩展运算符(...)。 + +```javascript + +let map = new Map([ + [1, 'one'], + [2, 'two'], + [3, 'three'], +]); + +[...map.keys()] +// [1, 2, 3] + +[...map.values()] +// ['one', 'two', 'three'] + +[...map.entries()] +// [[1,'one'], [2, 'two'], [3, 'three']] + +[...map] +// [[1,'one'], [2, 'two'], [3, 'three']] + +``` + +结合数组的map方法、filter方法,可以实现Map的遍历和过滤(Map本身没有map和filter方法)。 + +```javascript + +let map0 = new Map() + .set(1, 'a') + .set(2, 'b') + .set(3, 'c'); + +let map1 = new Map( + [...map0].filter(([k, v]) => k < 3) +); +// 产生Map结构 {1 => 'a', 2 => 'b'} + +let map2 = new Map( + [...map0].map(([k, v]) => [k * 2, '_' + v]) + ); +// 产生Map结构 {2 => '_a', 4 => '_b', 6 => '_c'} + +``` + 此外,Map还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历。 ```javascript map.forEach(function(value, key, map)) { - console.log("Key: %s, Value: %s", key, value); + console.log("Key: %s, Value: %s", key, value); }; ``` @@ -331,13 +573,13 @@ forEach方法还可以接受第二个参数,用来绑定this。 ```javascript var reporter = { - report: function(key, value) { - console.log("Key: %s, Value: %s", key, value); - } + report: function(key, value) { + console.log("Key: %s, Value: %s", key, value); + } }; map.forEach(function(value, key, map) { - this.report(key, value); + this.report(key, value); }, reporter); ``` @@ -346,7 +588,7 @@ map.forEach(function(value, key, map) { ## WeakMap -WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受原始类型的值作为键名。 +WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受原始类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。 WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap自动移除对应的键值对。典型应用是,一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除。基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。 @@ -370,4 +612,37 @@ console.log(value); // undefined ``` -WeakMap还有has和delete方法,但没有size属性,也无法遍历它的值,这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。 +WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有key()、values()和entries()方法),也没有size属性;二是无法清空,即不支持clear方法。这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。 + +WeakMap的一个用处是部署私有属性。 + +```javascript + +let _counter = new WeakMap(); +let _action = new WeakMap(); + +class Countdown { + constructor(counter, action) { + _counter.set(this, counter); + _action.set(this, action); + } + dec() { + let counter = _counter.get(this); + if (counter < 1) return; + counter--; + _counter.set(this, counter); + if (counter === 0) { + _action.get(this)(); + } + } +} + +let c = new Countdown(2, () => console.log('DONE')); + +c.dec() +c.dec() +// DONE + +``` + +上面代码中,Countdown类的两个内部属性_counter和_action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。 From e0203fcf6a3f991ed3f7b2f9913708ea836f3be8 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Thu, 8 Jan 2015 10:39:19 +0800 Subject: [PATCH 0017/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9set-map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 50b55494f..d243a7c9d 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -371,7 +371,7 @@ map.get(k2) // 222 由上可知,Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。 -如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map将其视为一个键。这对于NaN,以及+0和-0都成立。 +如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map将其视为一个键,包括0和-0。另外,虽然NaN不严格相等于自身,但Map将其视为同一个键。 ```javascript From 49ec7d0998deb6ac154cfde4e48fcff9eb2bf7c9 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sat, 10 Jan 2015 08:27:11 +0800 Subject: [PATCH 0018/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/set-map.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/set-map.md b/docs/set-map.md index d243a7c9d..2ec638c3c 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -32,7 +32,7 @@ items.size ``` -向Set加入值的时候,不会发生类型转换,5和“5”是两个不同的值。Set内部判断两个值是否精确相等,使用的是精确相等运算符(===)。这意味着,两个对象总是不相等的。 +向Set加入值的时候,不会发生类型转换,所以5和“5”是两个不同的值。Set内部判断两个值是否不同,使用的算法类似于精确相等运算符(===),唯一的例外是NaN等于自身。这意味着,两个对象总是不相等的。 ```javascript @@ -235,6 +235,21 @@ set = new Set([...set].filter(x => (x % 2) == 0)); ``` +因此使用Set,可以很容易地实现并集(Union)和交集(Intersect)。 + +```javascript + +let a = new Set([1,2,3]); +let b = new Set([4,3,2]); + +let union = new Set([...a, ...b]); +// [1,2,3,4] + +let intersect = new Set([...a].filter(x => b.has(x))); +// [2,3] + +``` + ## WeakSet WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别。 From 540642f75d1ed1c98721f018318f990b75106d12 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Mon, 12 Jan 2015 22:10:06 +0800 Subject: [PATCH 0019/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9docs/function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/function.md | 117 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/docs/function.md b/docs/function.md index e3e8feeda..8ecfd9fa3 100644 --- a/docs/function.md +++ b/docs/function.md @@ -65,6 +65,47 @@ var p = new Point(); ``` +定义了默认值的参数,必须是函数的尾部参数,其后不能再有其他无默认值的参数。这是因为有了默认值以后,该参数可以省略,只有位于尾部,才可能判断出到底省略了哪些参数。 + +```javascript + +// 以下两种写法都是错的 + +function f(x=5, y) { +} + +function f(x, y=5, z) { +} + +``` + +如果传入undefined,将触发该参数等于默认值,null则没有这个效果。 + +```javascript + +function foo(x=5, y=6){ + console.log(x,y); +} + +foo(undefined, null) +// 5 null + +``` + +上面代码中,x参数对应undefined,结果触发了默认值,y参数等于null,就没有触发默认值。 + +指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。 + +```javascript + +(function(a){}).length // 1 +(function(a=5){}).length // 0 +(function(a, b, c=5){}).length // 2 + +``` + +上面代码中,length属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。 + 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。 ```javascript @@ -188,14 +229,42 @@ add(...numbers) // 42 上面代码中,`array.push(...items)`和`add(...numbers)`这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。 -扩展运算符可以简化求出一个数组最大元素的写法。 +由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。 + +```javascript + +// ES5的写法 + +function f(x, y, z) { } +var args = [0, 1, 2]; +f.apply(null, args); + +// ES6的写法 + +function f(x, y, z) { } +var args = [0, 1, 2]; +f(...args); + +``` + +扩展运算符与正常的函数参数可以结合使用,非常灵活。 + +```javascript + +function f(v, w, x, y, z) { } +var args = [0, 1]; +f(-1, ...args, 2, ...[3]); + +``` + +下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法。 ```javascript -// ES5 +// ES5的写法 Math.max.apply(null, [14, 3, 77]) -// ES6 +// ES6的写法 Math.max(...[14, 3, 77]) // 等同于 @@ -205,6 +274,24 @@ Math.max(14, 3, 77); 上面代码表示,由于JavaScript不提供求数组最大元素的函数,所以只能套用Math.max函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用Math.max了。 +另一个例子是通过push函数,将一个数组添加到另一个数组的尾部。 + +```javascript + +// ES5的写法 +var arr1 = [0, 1, 2]; +var arr2 = [3, 4, 5]; +Array.prototype.push.apply(arr1, arr2); + +// ES6的写法 +var arr1 = [0, 1, 2]; +var arr2 = [3, 4, 5]; +arr1.push(...arr2); + +``` + +上面代码的ES5写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。有了扩展运算符,就可以直接将数组传入push方法。 + 扩展运算符还可以用于数组的赋值。 ```javascript @@ -219,6 +306,19 @@ d ``` +上面代码其实也提供了,将一个数组拷贝进另一个数组的便捷方法。 + +JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。 + +```javascript + +var dateFields = readDateFields(database); +var d = new Date(...dateFields); + +``` + +上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date。 + 扩展运算符还可以将字符串转为真正的数组。 ```javascript @@ -228,6 +328,17 @@ d ``` +任何类似数组的对象,都可以用扩展运算符转为真正的数组。 + +```javascript + +var nodeList = document.querySelectorAll('div'); +var array = [...nodeList]; + +``` + +上面代码中,querySelectorAll方法返回的是一个nodeList对象,扩展运算符可以将其转为真正的数组。 + 扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。 ```javascript From 29149c7450982ba966ae8755bbbcaae8a709bdd9 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 13 Jan 2015 09:51:16 +0800 Subject: [PATCH 0020/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9numbers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/array.md | 24 +++++++- docs/destructuring.md | 10 ++++ docs/function.md | 10 ++++ docs/number.md | 133 +++++++++++++++++++++++++++++++++++++++++- docs/object.md | 68 +++++++++++++++++---- 5 files changed, 229 insertions(+), 16 deletions(-) diff --git a/docs/array.md b/docs/array.md index 23bac7a5b..fcb92a2c0 100644 --- a/docs/array.md +++ b/docs/array.md @@ -16,6 +16,27 @@ Array.from(ps).forEach(function (p) { 上面代码中,querySelectorAll方法返回的是一个类似数组的对象,只有将这个对象转为真正的数组,才能使用forEach方法。 +Array.from方法可以将函数的arguments对象,转为数组。 + +```javascript + +function foo() { + var args = Array.from( arguments ); +} + +foo( "a", "b", "c" ); + +``` + +任何有length属性的对象,都可以通过Array.from方法转为数组。 + +```javascript + +Array.from({ 0: "a", 1: "b", 2: "c", length: 3 }); +// [ "a", "b" , "c" ] + +``` + Array.from()还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理。 ```JavaScript @@ -43,11 +64,12 @@ Array.of()方法用于将一组值,转换为数组。 ```javaScript Array.of(3, 11, 8) // [3,11,8] +Array.of(3) // [3] Array.of(3).length // 1 ``` -这个函数的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。 +这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。 ```javascript diff --git a/docs/destructuring.md b/docs/destructuring.md index 36ad9e1bc..a6db313eb 100644 --- a/docs/destructuring.md +++ b/docs/destructuring.md @@ -199,6 +199,16 @@ var x; ``` +对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。 + +```javascript + +let { log, sin, cos } = Math; + +``` + +上面代码将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。 + ## 用途 变量的解构赋值用途很多。 diff --git a/docs/function.md b/docs/function.md index 8ecfd9fa3..a8a1bb8fd 100644 --- a/docs/function.md +++ b/docs/function.md @@ -208,6 +208,16 @@ function f(a, ...b, c) { ``` +函数的length属性,不包括rest参数。 + +```javascript + +(function(a) {}).length // 1 +(function(...a) {}).length // 0 +(function(a, ...b) {}).length // 1 + +``` + ## 扩展运算符 扩展运算符(spread)是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。该运算符主要用于函数调用。 diff --git a/docs/number.md b/docs/number.md index 28a321e29..828f23bd1 100644 --- a/docs/number.md +++ b/docs/number.md @@ -17,7 +17,71 @@ ES6提供了二进制和八进制数值的新的写法,分别用前缀0b和0o ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法,用来检查Infinite和NaN这两个特殊值。 -它们与传统的isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,非数值一律返回false。 +Number.isFinite()用来检查一个数值是否非无穷(infinity)。 + +```javascript + +Number.isFinite(15); // true +Number.isFinite(0.8); // true +Number.isFinite(NaN); // false +Number.isFinite(Infinity); // false +Number.isFinite(-Infinity); // false +Number.isFinite("foo"); // false +Number.isFinite("15"); // false +Number.isFinite(true); // false + +``` + +ES5通过下面的代码,部署Number.isFinite方法。 + +```javascript + +(function (global) { + var global_isFinite = global.isFinite; + + Object.defineProperty(Number, 'isFinite', { + value: function isFinite(value) { + return typeof value === 'number' && global_isFinite(value); + }, + configurable: true, + enumerable: false, + writable: true + }); +})(this); + +``` + +Number.isNaN()用来检查一个值是否为NaN。 + +```javascript + +Number.isNaN(NaN); // true +Number.isNaN(15); // false +Number.isNaN("15"); // false +Number.isNaN(true); // false + +``` + +ES5通过下面的代码,部署Number.isNaN()。 + +```javascript + +(function (global) { + var global_isNaN = global.isNaN; + + Object.defineProperty(Number, 'isNaN', { + value: function isNaN(value) { + return typeof value === 'number' && global_isNaN(value); + }, + configurable: true, + enumerable: false, + writable: true + }); +})(this); + +``` + +它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,非数值一律返回false。 ```javascript @@ -60,6 +124,30 @@ Number.isInteger()用来判断一个值是否为整数。需要注意的是, Number.isInteger(25) // true Number.isInteger(25.0) // true Number.isInteger(25.1) // false +Number.isInteger("15") // false +Number.isInteger(true) // false + +``` + +ES5通过下面的代码,部署Number.isInteger()。 + +```javascript + +(function (global) { + var floor = Math.floor, + isFinite = global.isFinite; + + Object.defineProperty(Number, 'isInteger', { + value: function isInteger(value) { + return typeof value === 'number' && isFinite(value) && + value > -9007199254740992 && value < 9007199254740992 && + floor(value) === value; + }, + configurable: true, + enumerable: false, + writable: true + }); +})(this); ``` @@ -93,7 +181,47 @@ Math.trunc(-4.9) // -4 ``` -**(2)数学方法** +**(2)Math.sign()** + +Math.sign方法用来判断一个数到底是正数、负数、还是零。如果参数为正数,返回+1;参数为负数,返回-1;参数为0,返回0;参数为NaN,返回NaN。 + +```javascript + +Math.sign(-5) // -1 +Math.sign(5) // +1 +Math.sign(0) // +0 +Math.sign(-) // -0 +Math.sign(NaN) // NaN + +``` + +ES5通过下面的代码,可以部署Math.sign()。 + +```javascript + +(function (global) { + var isNaN = Number.isNaN; + + Object.defineProperty(Math, 'sign', { + value: function sign(value) { + var n = +value; + if (isNaN(n)) + return n /* NaN */; + + if (n === 0) + return n; // Keep the sign of the zero. + + return (n < 0) ? -1 : 1; + }, + configurable: true, + enumerable: false, + writable: true + }); +})(this); + +``` + +**(3)数学方法** ES6在Math对象上还提供了许多新的数学方法。 @@ -110,5 +238,4 @@ ES6在Math对象上还提供了许多新的数学方法。 - Math.log1p(x) 返回1 + x的自然对数 - Math.log10(x) 返回以10为底的x的对数 - Math.log2(x) 返回以2为底的x的对数 -- Math.sign(x) 如果x为负返回-1,x为0返回0,x为正返回1 - Math.tanh(x) 返回x的双曲正切(hyperbolic tangent) diff --git a/docs/object.md b/docs/object.md index 11408ceca..f6451fcb9 100644 --- a/docs/object.md +++ b/docs/object.md @@ -6,6 +6,42 @@ ES6允许直接写入变量和函数,作为对象的属性和方法。这样 ```javascript +function f( x, y ) { + return { x, y }; +} + +// 等同于 + +function f( x, y ) { + return { x: x, y: y }; +} + +``` + +上面是属性简写的例子,方法也可以简写。 + +```javascript + +var o = { + method() { + return "Hello!"; + } +}; + +// 等同于 + +var o = { + method: function() { + return "Hello!"; + } +}; + +``` + +下面是一个更实际的例子。 + +```javascript + var Person = { name: '张三', @@ -36,18 +72,6 @@ getPoint() ``` -下面是一个类似的例子。 - -```javascript - -let x = 4; -let y = 1; - -// 下行等同于 let obj = { x: x, y: y }; -let obj = { x, y }; - -``` - ## 属性名表达式 JavaScript语言定义对象的属性,有两种方法。 @@ -133,6 +157,26 @@ Object.is(NaN, NaN) // true ``` +ES5可以通过下面的代码,部署Object.is()。 + +```javascript + +Object.defineProperty(Object, 'is', { + value: function(x, y) { + if (x === y) { + // 针对+0 不等于 -0的情况 + return x !== 0 || 1 / x === 1 / y; + } + // 针对NaN的情况 + return x !== x && y !== y; + }, + configurable: true, + enumerable: false, + writable: true +}); + +``` + ## Object.assign() Object.assign方法用来将源对象(source)的所有可枚举属性,复制到目标对象(target)。它至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。只要有一个参数不是对象,就会抛出TypeError错误。 From 4d044952dc703d764485f791d65068bdb85c1b44 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Wed, 14 Jan 2015 12:10:21 +0800 Subject: [PATCH 0021/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9string/contains?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E6=94=B9=E4=B8=BAincludes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/string.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/string.md b/docs/string.md index 7c6b403bf..791bd340d 100644 --- a/docs/string.md +++ b/docs/string.md @@ -265,11 +265,11 @@ normalize方法可以接受四个参数。 不过,normalize方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过Unicode编号区间判断。 -## contains(), startsWith(), endsWith() +## includes(), startsWith(), endsWith() 传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。 -- **contains()**:返回布尔值,表示是否找到了参数字符串。 +- **includes()**:返回布尔值,表示是否找到了参数字符串。 - **startsWith()**:返回布尔值,表示参数字符串是否在源字符串的头部。 - **endsWith()**:返回布尔值,表示参数字符串是否在源字符串的尾部。 @@ -279,7 +279,7 @@ var s = "Hello world!"; s.startsWith("Hello") // true s.endsWith("!") // true -s.contains("o") // true +s.includes("o") // true ``` @@ -291,7 +291,7 @@ var s = "Hello world!"; s.startsWith("o", 4) // true s.endsWith("o", 8) // true -s.contains("o", 8) // false +s.includes("o", 8) // false ``` From 93a5cdd882660c9e5d46e8a1e95ee6ce085c4fe7 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 16 Jan 2015 08:01:55 +0800 Subject: [PATCH 0022/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9string/startsWith?= =?UTF-8?q?=E7=9A=84=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/string.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/string.md b/docs/string.md index 791bd340d..3f4c0420c 100644 --- a/docs/string.md +++ b/docs/string.md @@ -289,9 +289,9 @@ s.includes("o") // true var s = "Hello world!"; -s.startsWith("o", 4) // true -s.endsWith("o", 8) // true -s.includes("o", 8) // false +s.startsWith("world", 6) // true +s.endsWith("Hello", 5) // true +s.includes("Hello", 6) // false ``` From 4f7f2ea8ebb524c9d5bf69e8e516e10278fe3b46 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 25 Jan 2015 14:24:20 +0800 Subject: [PATCH 0023/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/destructuring.md | 2 +- docs/generator.md | 53 +++++++++++++++++++++++++++++++++++++++++++ docs/promise.md | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/docs/destructuring.md b/docs/destructuring.md index a6db313eb..9066e9388 100644 --- a/docs/destructuring.md +++ b/docs/destructuring.md @@ -108,7 +108,7 @@ a // "a" ``` -事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的结构赋值。 +事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。 ## 对象的解构赋值 diff --git a/docs/generator.md b/docs/generator.md index 5ecb57758..339611a32 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -74,6 +74,59 @@ setTimeout(function () { 上面代码中,函数f如果是普通函数,在为变量generator赋值时就会执行。但是,函数f是一个Generator函数,就变成只有调用next方法时,函数f才会执行。 +另外需要注意,yield语句不能用在普通函数中,否则会报错。 + +```javascript + +(function (){ + yield 1; +})() +// SyntaxError: Unexpected number + +``` + +上面代码在一个普通函数中使用yield语句,结果产生一个句法错误。 + +下面是另外一个例子。 + +```javascript + +var arr = [1, [[2, 3], 4], [5, 6]]; + +var flat = function* (a){ + a.forEach(function(item){ + if (typeof item !== 'number'){ + yield* flat(item); + } else { + yield item; + } + } +}; + +for (var f of flat(arr)){ + console.log(f); +} + +``` + +上面代码也会产生句法错误,因为forEach方法的参数是一个普通函数,但是在里面使用了yield语句。一种修改方法是改用for循环。 + +```javascript + +var flat = function* (a){ + var length = a.length; + for(var i =0;i Date: Sun, 25 Jan 2015 22:58:24 +0800 Subject: [PATCH 0024/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9docs/generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++ docs/reference.md | 1 + 2 files changed, 55 insertions(+) diff --git a/docs/generator.md b/docs/generator.md index 339611a32..f2b9cf35b 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -113,6 +113,8 @@ for (var f of flat(arr)){ ```javascript +var arr = [1, [[2, 3], 4], [5, 6]]; + var flat = function* (a){ var length = a.length; for(var i =0;i Date: Tue, 27 Jan 2015 21:52:28 +0800 Subject: [PATCH 0025/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9string/=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=AD=97=E7=AC=A6=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 18 +++++++ docs/generator.md | 15 ++++++ docs/iterator.md | 98 +++++++++++++++++++++++++++++++------- docs/reference.md | 7 +++ docs/set-map.md | 49 +++++++++++++++++-- docs/string.md | 118 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 284 insertions(+), 21 deletions(-) diff --git a/docs/class.md b/docs/class.md index f8832e9d9..32a0af008 100644 --- a/docs/class.md +++ b/docs/class.md @@ -153,6 +153,24 @@ import { lastName as surname } from './profile'; ``` +ES6支持多重加载,即所加载的模块中又加载其他模块。 + +```javascript + +import { Vehicle } from './Vehicle'; + +class Car extends Vehicle { + move () { + console.log(this.name + ' is spinning wheels...') + } +} + +export { Car } + +``` + +上面的模块先加载Vehicle模块,然后在其基础上添加了move方法,再作为一个新模块输出。 + **(2)模块的整体输入,module命令** export命令除了输出变量,还可以输出方法或类(class)。下面是一个circle.js文件,它输出两个方法area和circumference。 diff --git a/docs/generator.md b/docs/generator.md index f2b9cf35b..34c84d9b8 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -52,6 +52,21 @@ hw.next() 总结一下,Generator函数使用iterator接口,每次调用next方法的返回值,就是一个标准的iterator返回值:有着value和done两个属性的对象。其中,value是yield语句后面那个表达式的值,done是一个布尔值,表示是否遍历结束。 +由于Generator函数本身就能生成遍历器,所以它的Symbol.iterator属性指向自身。 + +```javascript + +function* gen(){ + // some code +} + +gen[Symbol.iterator]() === gen +// true + +``` + +上面代码中,gen是一个Generator函数,它的Symbol.iterator属性就指向它自己。 + 由于Generator函数返回的遍历器,只有调用next方法才会遍历下一个成员,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志,next方法遇到yield,就会暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性的值。当下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。如果没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,作为value属性的值,如果该函数没有return语句,则value属性的值为undefined。另一方面,由于yield后面的表达式,直到调用next方法时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。 yield语句与return语句有点像,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说Generator生成了一系列的值,这也就是它的名称的来历(在英语中,generator这个词是“生成器”的意思)。 diff --git a/docs/iterator.md b/docs/iterator.md index d63d61c69..ebe3589f5 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -2,6 +2,8 @@ ## Iterator(遍历器) +### 语法 + 遍历器(Iterator)是一种接口规格,任何对象只要部署这个接口,就可以完成遍历操作。它的作用有两个,一是为各种数据结构,提供一个统一的、简便的接口,二是使得对象的属性能够按某种次序排列。在ES6中,遍历操作特指for...of循环,即Iterator接口主要供for...of循环使用。 遍历器提供了一个指针,指向当前对象的某个属性,使用next方法,就可以将指针移动到下一个属性。next方法返回一个包含value和done两个属性的对象。其中,value属性是当前遍历位置的值,done属性是一个布尔值,表示遍历是否结束。下面是一个模拟next方法返回值的例子。 @@ -51,9 +53,15 @@ it.next().value // '2' ``` -上面的例子,只是为了说明next方法返回值的结构。Iterator接口返回的遍历器,原生具备next方法,不用自己部署。所以,真正需要部署的是Iterator接口,让其返回一个遍历器。在ES6中,有三类数据结构原生具备Iterator接口:数组、类似数组的对象、Set和Map结构。除此之外,其他数据结构(主要是对象)的Iterator接口都需要自己部署。 +上面的例子,说明了next方法返回值的结构:value和done两个属性。 + +### Iterator接口的部署 + +具有Iterator接口的对象,都能被for...of循环遍历(见后文的介绍)。所谓Iterator接口,就是指它会返回一个遍历器对象,该对象具备next方法,每次调用该方法,会依次返回一个具有上节提到的value和done两个属性的新对象,指向原对象的一个成员。 + +在ES6中,有三类数据结构原生具备Iterator接口:数组、类似数组的对象、Set和Map结构。除此之外,其他数据结构(主要是对象)的Iterator接口都需要自己部署。其他对象需要手动部署Iterator接口,让其返回一个遍历器。 -下面就是如何部署Iterator接口。一个对象如果要有Iterator接口,必须部署一个@@iterator方法(原型链上的对象具有该方法也可),该方法部署在一个键名为`Symbol.iterator`的属性上,对应的键值是一个函数,该函数返回一个遍历器对象。 +一个对象如果要有Iterator接口,必须部署一个@@iterator方法(原型链上的对象具有该方法也可),该方法部署在一个键名为`Symbol.iterator`的属性上,对应的键值是一个函数,该函数返回一个遍历器对象。 ```javascript @@ -69,7 +77,59 @@ class MySpecialTree { 上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一节)。这里要注意,@@iterator的键名是`Symbol.iterator`,键值是一个方法(函数),该方法执行后,返回一个当前对象的遍历器。 -下面是为对象添加Iterator接口的例子。 +下面是一个例子。 + +```javascript + +function O(value){ + this.value = value; + this.next = null; +} + +O.prototype[Symbol.iterator] = function(){ + + var iterator = { + next: next + }; + + var current = this; + + function next(){ + if (current){ + var value = current.value; + var done = current == null; + current = current.next; + return { + done: done, + value: value + } + } else { + return { + done: true + } + } + } + return iterator; +} + +var one = new O(1); +var two = new O(2); +var three = new O(3); +one.next = two; +two.next = three; + +for (var i of one){ + console.log(i) +} +// 1 +// 2 +// 3 + +``` + +上面代码首先在构造函数的原型链上部署Symbol.iterator方法,调用该方法会返回遍历器对象iterator,调用该对象的next方法,在返回一个值的同时,自动将内部指针移到下一个实例。 + +下面是另一个为对象添加Iterator接口的例子。 ```javascript @@ -95,6 +155,22 @@ let obj = { ``` +如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。 + +```javascript + +var obj = {}; + +obj[Symbol.iterator] = () => 1; + +[...obj] // TypeError: [] is not a function + +``` + +上面代码中,变量obj的@@iterator方法返回的不是遍历器,因此报错。 + +### 原生具备iterator接口的数据结构 + 《数组的扩展》一章中提到,ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器。 ```javascript @@ -159,6 +235,8 @@ str // "hi" 上面代码中,字符串str的`Symbol.iterator`方法被修改了,所以扩展运算符(...)返回的值变成了bye,而字符串本身还是hi。 +### Iterator接口与Generator函数 + 部署`Symbol.iterator`方法的最简单实现,还是使用下一节要提到的Generator函数。 ```javascript @@ -189,20 +267,6 @@ for (let x of obj) { ``` -如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。 - -```javascript - -var obj = {}; - -obj[Symbol.iterator] = () => 1; - -[...obj] // TypeError: [] is not a function - -``` - -上面代码中,变量obj的@@iterator方法返回的不是遍历器,因此报错。 - ## for...of循环 ES6中,一个对象只要部署了@@iterator方法,就被视为具有iterator接口,就可以用for...of循环遍历它的值。也就是说,for...of循环内部调用是原对象的`Symbol.iterator`方法。 diff --git a/docs/reference.md b/docs/reference.md index bd957fda4..80b05899d 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -19,6 +19,8 @@ - Luke Hoban, [ES6 features](https://github.com/lukehoban/es6features) - Traceur-compiler, [Language Features](https://github.com/google/traceur-compiler/wiki/LanguageFeatures): Traceur文档列出的一些ES6例子 - Axel Rauschmayer, [ECMAScript 6: what’s next for JavaScript?](https://speakerdeck.com/rauschma/ecmascript-6-whats-next-for-javascript-august-2014): 关于ES6新增语法的综合介绍,有很多例子 +- Toby Ho, [ES6 in io.js](http://davidwalsh.name/es6-io) + ## 语法点 @@ -31,7 +33,12 @@ - Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 - Axel Rauschmayer, [ECMAScript 6: maps and sets](http://www.2ality.com/2015/01/es6-maps-sets.html): Set和Map结构的详细介绍 + +## 字符串 + - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 +- Nicholas C. Zakas, [A critical review of ECMAScript 6 quasi-literals](http://www.nczonline.net/blog/2012/08/01/a-critical-review-of-ecmascript-6-quasi-literals/) +- Mozilla Developer Network, [Template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings) ## Object diff --git a/docs/set-map.md b/docs/set-map.md index 2ec638c3c..51023e454 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -256,6 +256,16 @@ WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set 首先,WeakSet的成员只能是对象,而不能是其他类型的值。其次,WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。 +```javascript + +var ws = new WeakSet(); +ws.add(1) +// TypeError: Invalid value used in weak set + +``` + +上面代码试图向WeakSet添加一个数值,结果报错。 + WeakSet是一个构造函数,可以使用new命令,创建WeakSet数据结构。 ```javascript @@ -303,6 +313,25 @@ ws.clear(); ``` +WeakSet没有size属性,没有办法遍历它的成员。 + +```javascript + +ws.size +// undefined + +ws.forEach(function(item){ console.log('WeakSet has ' + item)}) +// TypeError: undefined is not a function + +ws.forEach +// undefined + +``` + +上面代码试图获取size和forEach属性,结果都不能成功。 + +WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保存成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。 + ## Map **(1)基本用法** @@ -611,24 +640,36 @@ WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制 ```javascript -var map = new WeakMap(); +var wm = new WeakMap(); var element = document.querySelector(".element"); -map.set(element, "Original"); +wm.set(element, "Original"); -var value = map.get(element); +var value = wm.get(element); console.log(value); // "Original" element.parentNode.removeChild(element); element = null; -value = map.get(element); +value = wm.get(element); console.log(value); // undefined ``` WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有key()、values()和entries()方法),也没有size属性;二是无法清空,即不支持clear方法。这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。 +```javascript + +var wm = new WeakMap(); + +wm.size +// undefined + +wm.forEach +// undefined + +``` + WeakMap的一个用处是部署私有属性。 ```javascript diff --git a/docs/string.md b/docs/string.md index 3f4c0420c..3eb18afc6 100644 --- a/docs/string.md +++ b/docs/string.md @@ -402,6 +402,15 @@ console.log(`${obj.x + obj.y}`) ``` +如果模板字符串中的变量没有声明,将报错。 + +```javascript + +var msg = `Hello, ${place}`; +// throws error + +``` + 模板字符串使得字符串与变量的结合,变得容易。下面是一个例子。 ```javascript @@ -412,3 +421,112 @@ if (x > MAX) { } ``` + +模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。 + +```javascript + +var a = 5; +var b = 10; + +tag`Hello ${ a + b } world ${ a * b}`; + +``` + +上面代码中,模板字符串前面有一个函数tag,整个表达式将返回tag处理模板字符串后的返回值。 + +函数tag依次接受三个参数。第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。第一个参数之后的参数,都是模板字符串各个变量被替换后的值。 + +- 第一个参数:['Hello ', ' world '] +- 第二个参数: 15 +- 第三个参数:50 + +```javascript + +var a = 5; +var b = 10; + +function tag(s, v1, v2) { + console.log(s[0]); // "Hello " + console.log(s[1]); // " world " + console.log(v1); // 15 + console.log(v2); // 50 + + return "OK"; +} + +tag`Hello ${ a + b } world ${ a * b}`; +// "OK" + +``` + +下面是一个更复杂的例子。 + +```javascript + +var total = 30; +var msg = passthru`The total is ${total} (${total*1.05} with tax)`; + +function passthru(literals) { + var result = ""; + var i = 0; + + while (i < literals.length) { + result += literals[i++]; + if (i < arguments.length) { + result += arguments[i]; + } + } + + return result; + +} + +msg +// "The total is 30 (31.5 with tax)" + +``` + +上面这个例子展示了,如何将各个参数按照原来的位置拼合回去。 + +处理函数的第一个参数,还有一个raw属性。它也是一个数组,成员与处理函数的第一个参数完全一致,唯一的区别是字符串被转义前的原始格式,这是为了模板函数处理的方便而提供的。 + +```javascript + +tag`First line\nSecond line` + +``` + +上面代码中,tag函数的第一个参数是一个数组`["First line\nSecond line"]`,这个数组有一个raw属性,等于`["First line\\nSecond line"]`,两者唯一的区别就是斜杠被转义了。 + +function tag(strings) { + console.log(strings.raw[0]); + // "First line\\nSecond line" +} + +## String.raw() + +String.raw方法,往往用来充当模板字符串的处理函数,返回字符串被转义前的原始格式。 + +```javascript + +String.raw`Hi\n${2+3}!`; +// "Hi\\n5!" + +String.raw`Hi\u000A!`; +// 'Hi\\u000A!' + +``` + +String.raw方法也可以正常的函数形式使用。这时,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组。 + +```javascript + +String.raw({ raw: 'test' }, 0, 1, 2); +// 't0e1s2t' + + +// 等同于 +String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2); + +``` From b06c2f1f6d5e0c1dd92581d45c19c6034c9c4e77 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Wed, 28 Jan 2015 21:48:15 +0800 Subject: [PATCH 0026/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9docs/generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/docs/generator.md b/docs/generator.md index 34c84d9b8..5e493c74c 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -324,6 +324,48 @@ function* g(){ ``` +如果Generator函数内部没有定义catch,那么throw方法抛出的错误,将被函数体的catch捕获。 + +```javascript + +function *foo() { } + +var it = foo(); +try { + it.throw( "Oops!" ); +} catch (err) { + console.log( "Error: " + err ); // Error: Oops! +} + +``` + +上面代码中,foo函数内部没有任何语句,throw抛出的错误被函数体外的catch捕获。 + +反过来,Generator函数内抛出的错误,也可以被函数体外的catch捕获。 + +```javascript + +function *foo() { + var x = yield 3; + var y = x.toUpperCase(); + yield y; +} + +var it = foo(); + +it.next(); // { value:3, done:false } + +try { + it.next( 42 ); +} +catch (err) { + console.log( err ); +} + +``` + +上面代码中,第二个next方法向函数体内传入一个参数42,数值是没有toUpperCase方法的,所以会抛出一个TypeError错误,被函数体外的catch捕获。 + 一旦Generator执行过程中抛出错误,就不会再执行下去了。如果此后还调用next方法,将一直返回发生错误前的那个值。 ```javascript @@ -414,6 +456,35 @@ gen().next() // { value:"a", done:false } 上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器。 +如果被代理的Generator函数有return语句,那么就可以向代理它的Generator函数返回数据。 + +```javascript + +function *foo() { + yield 2; + yield 3; + return "foo"; +} + +function *bar() { + yield 1; + var v = yield *foo(); + console.log( "v: " + v ); + yield 4; +} + +var it = bar(); + +it.next(); // +it.next(); // +it.next(); // +it.next(); // "v: foo" +it.next(); // + +``` + +上面代码在第四次调用next方法的时候,屏幕上会有输出,这是因为函数foo的return语句,向函数bar提供了返回值。 + `yield*`命令可以很方便地取出嵌套数组的所有成员。 ```javascript From d3fa544cea9303ccca4d5870eeed856d9804e8fb Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Wed, 4 Feb 2015 17:59:15 +0800 Subject: [PATCH 0027/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9function/rest=20&?= =?UTF-8?q?=20spread?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/function.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++ docs/reference.md | 1 + 2 files changed, 64 insertions(+) diff --git a/docs/function.md b/docs/function.md index a8a1bb8fd..b5d7cf02a 100644 --- a/docs/function.md +++ b/docs/function.md @@ -239,6 +239,14 @@ add(...numbers) // 42 上面代码中,`array.push(...items)`和`add(...numbers)`这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。 +下面是Date函数的参数使用扩展运算符的例子。 + +```javascript + +const date = new Date(...[2015, 1, 1]); + +``` + 由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。 ```javascript @@ -318,6 +326,44 @@ d 上面代码其实也提供了,将一个数组拷贝进另一个数组的便捷方法。 +扩展运算符也可以与解构赋值结合起来,用于生成数组。 + +```javascript + +const [first, ...rest] = [1, 2, 3, 4, 5]; +first // 1 +rest // [2, 3, 4, 5] + +const [first, ...rest] = []; +first // undefined +rest // []: + +const [first, ...rest] = ["foo"]; +first // "foo" +rest // [] + +const [first, ...rest] = ["foo", "bar"]; +first // "foo" +rest // ["bar"] + +const [first, ...rest] = ["foo", "bar", "baz"]; +first // "foo" +rest // ["bar","baz"] + +``` + +如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。 + +```javascript + +const [...butLast, last] = [1, 2, 3, 4, 5]; +// 报错 + +const [first, ..., last] = [1, 2, 3, 4, 5]; +// 报错 + +``` + JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。 ```javascript @@ -432,6 +478,7 @@ var sum = (num1, num2) => { return num1 + num2; } var getTempItem = id => ({ id: id, name: "Temp" }); ``` + 箭头函数的一个用处是简化回调函数。 ```javascript @@ -460,6 +507,22 @@ var result = values.sort((a, b) => a - b); ``` +下面是rest参数与箭头函数结合的例子。 + +```javascript + +const numbers = (...nums) => nums; + +numbers(1, 2, 3, 4, 5) +// [1,2,3,4,5] + +const headAndTail = (head, ...tail) => [head, tail]; + +headAndTail(1, 2, 3, 4, 5) +// [1,[2,3,4,5]] + +``` + 箭头函数有几个使用注意点。 - 函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象。 diff --git a/docs/reference.md b/docs/reference.md index 80b05899d..81a358990 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -33,6 +33,7 @@ - Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 - Axel Rauschmayer, [ECMAScript 6: maps and sets](http://www.2ality.com/2015/01/es6-maps-sets.html): Set和Map结构的详细介绍 +- Ragan Wald, [Destructuring and Recursion in ES6](http://raganwald.com/2015/02/02/destructuring.html): rest参数和扩展运算符的详细介绍 ## 字符串 From aa30945f1bc00a7d802847d62868792a969f3672 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Thu, 5 Feb 2015 23:08:48 +0800 Subject: [PATCH 0028/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9object/proxy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/let.md | 13 +++++++++++++ docs/object.md | 35 ++++++++++++++++++++++++++++++++++- docs/reference.md | 4 +++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/docs/let.md b/docs/let.md index 7e6862773..0f8064ab8 100644 --- a/docs/let.md +++ b/docs/let.md @@ -75,6 +75,19 @@ function do_something() { 上面代码在声明foo之前,就使用这个变量,结果会抛出一个错误。 +这也意味着typeof不再是一个百分之百安全的操作。 + +```javascript + +if (1) { + typeof x; // ReferenceError + let x; +} + +``` + +上面代码中,由于块级作用域内typeof运行时,x还没有声明,所以会抛出一个ReferenceError。 + 注意,let不允许在相同作用域内,重复声明同一个变量。 ```javascript diff --git a/docs/object.md b/docs/object.md index f6451fcb9..44ae792e3 100644 --- a/docs/object.md +++ b/docs/object.md @@ -590,7 +590,7 @@ obj.time // 35 上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所有根据原型链,会在proxy对象上读取该属性,导致被拦截。 -对于没有设置拦截的操作,则直接落在目标函数上,按照原先的方式产生结果。 +对于没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。 下面是另一个拦截读取操作的例子。 @@ -617,6 +617,39 @@ proxy.age // 抛出一个错误 上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined。 +利用proxy,可以将读取属性的操作(get),转变为执行某个函数。 + +```javascript + +var pipe = (function () { + var pipe; + return function (value) { + pipe = []; + return new Proxy({}, { + get: function (pipeObject, fnName) { + if (fnName == "get") { + return pipe.reduce(function (val, fn) { + return fn(val); + }, value); + } + pipe.push(window[fnName]); + return pipeObject; + } + }); + } +}()); + +var double = function (n) { return n*2 }; +var pow = function (n) { return n*n }; +var reverseInt = function (n) { return n.toString().split('').reverse().join('')|0 }; + +pipe(3) . double . pow . reverseInt . get +// 63 + +``` + +上面代码设置Proxy以后,达到了将函数名链式使用的效果。 + 除了取值函数get,Proxy还可以设置存值函数set,用来拦截某个属性的赋值行为。假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性值符合要求。 ```javascript diff --git a/docs/reference.md b/docs/reference.md index 81a358990..0bb3e7246 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -24,7 +24,8 @@ ## 语法点 -- Kyle Simpson, [For and against `let`](http://davidwalsh.name/for-and-against-let): 讨论let命令的作用域 +- Kyle Simpson, [For and against `let`](http://davidwalsh.name/for-and-against-let): 讨论let命令的作用域 +- kangax, [Why `typeof` is no longer “safe”](http://es-discourse.com/t/why-typeof-is-no-longer-safe/15): 讨论在块级作用域内,let命令的变量声明和赋值的行为 - Nick Fitzgerald, [Destructuring Assignment in ECMAScript 6](http://fitzgeraldnick.com/weblog/50/): 详细介绍解构赋值的用法 - Nicholas C. Zakas, [Understanding ECMAScript 6 arrow functions](http://www.nczonline.net/blog/2013/09/10/understanding-ecmascript-6-arrow-functions/) - Jack Franklin, [Real Life ES6 - Arrow Functions](http://javascriptplayground.com/blog/2014/04/real-life-es6-arrow-fn/) @@ -48,6 +49,7 @@ - Sella Rafaeli, [Native JavaScript Data-Binding](http://www.sellarafaeli.com/blog/native_javascript_data_binding): 如何使用Object.observe方法,实现数据对象与DOM对象的双向绑定 - Axel Rauschmayer, [Symbols in ECMAScript 6](http://www.2ality.com/2014/12/es6-symbols.html): Symbol简介 - Axel Rauschmayer, [Meta programming with ECMAScript 6 proxies](http://www.2ality.com/2014/12/es6-proxies.html): Proxy详解 +- Daniel Zautner, [Meta-programming JavaScript Using Proxies](http://dzautner.com/meta-programming-javascript-using-proxies/):使用Proxy实现元编程 ## Iterator和Generator From 8a223e4413a1ce95e000dbaf04c0f45096abdd05 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 8 Feb 2015 20:48:39 +0800 Subject: [PATCH 0029/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9let?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/function.md | 9 +++++ docs/let.md | 98 +++++++++++++++++++++++++++++++++++++++++++++-- docs/reference.md | 2 + 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/docs/function.md b/docs/function.md index b5d7cf02a..ecf8f7db7 100644 --- a/docs/function.md +++ b/docs/function.md @@ -375,6 +375,15 @@ var d = new Date(...dateFields); 上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date。 +除了扩展数组,扩展运算符还可以将一个数值扩展成数值。 + +```javascript + +[...5] +// [0, 1, 2, 3, 4, 5] + +``` + 扩展运算符还可以将字符串转为真正的数组。 ```javascript diff --git a/docs/let.md b/docs/let.md index 0f8064ab8..9cd7e0adc 100644 --- a/docs/let.md +++ b/docs/let.md @@ -88,20 +88,85 @@ if (1) { 上面代码中,由于块级作用域内typeof运行时,x还没有声明,所以会抛出一个ReferenceError。 +总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。 + +```javascript + +if (true) { + // TDZ开始 + tmp = 'abc'; // ReferenceError + console.log(tmp); // ReferenceError + + let tmp; // TDZ结束 + console.log(tmp); // undefined + + tmp = 123; + console.log(tmp); // 123 +} + +``` + +上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。 + +有些“死区”比较隐蔽,不太容易发现。 + +```javascript + +function bar(x=y, y=2) { + return [x, y]; +} + +bar(); // 报错 + +``` + +上面代码中,调用bar函数之所以报错,是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。 + +```javascript + +let foo = 'outer'; + +function bar(func = x => foo) { + let foo = 'inner'; + console.log(func()); // outer +} + +bar(); + +``` + +上面代码中,函数bar的参数func,默认是一个匿名函数,返回值为foo。这个匿名函数运行时,foo只在函数体外声明,内层的声明还没执行,因此foo指向函数体外的声明,输出outer。 + 注意,let不允许在相同作用域内,重复声明同一个变量。 ```javascript // 报错 { - let a = 10; - var a = 1; + let a = 10; + var a = 1; } // 报错 { - let a = 10; - let a = 1; + let a = 10; + let a = 1; +} + +``` + +因此,不能在函数内部重新声明参数。 + +```javascript + +function func(arg) { + let arg; // 报错 +} + +function func(arg) { + { + let arg; // 不报错 + } } ``` @@ -205,3 +270,28 @@ const message = "Goodbye!"; const age = 30; ``` + +由于const命令只是指向变量所在的地址,所以将一个对象声明为常量必须非常小心。 + +```javascript + +const foo = {}; +foo.prop = 123; + +foo.prop +// 123 + +foo = {} // 不起作用 + +``` + +上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。如果真的想将对象冻结,应该使用Object.freeze方法。 + +```javascript + +const foo = Object.freeze({}); +foo.prop = 123; // 不起作用 + +``` + +上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用。 diff --git a/docs/reference.md b/docs/reference.md index 0bb3e7246..d89a6d9c1 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -26,6 +26,7 @@ - Kyle Simpson, [For and against `let`](http://davidwalsh.name/for-and-against-let): 讨论let命令的作用域 - kangax, [Why `typeof` is no longer “safe”](http://es-discourse.com/t/why-typeof-is-no-longer-safe/15): 讨论在块级作用域内,let命令的变量声明和赋值的行为 +- Axel Rauschmayer, [Variables and scoping in ECMAScript 6](http://www.2ality.com/2015/02/es6-scoping.html): 讨论块级作用域与let和const的行为 - Nick Fitzgerald, [Destructuring Assignment in ECMAScript 6](http://fitzgeraldnick.com/weblog/50/): 详细介绍解构赋值的用法 - Nicholas C. Zakas, [Understanding ECMAScript 6 arrow functions](http://www.nczonline.net/blog/2013/09/10/understanding-ecmascript-6-arrow-functions/) - Jack Franklin, [Real Life ES6 - Arrow Functions](http://javascriptplayground.com/blog/2014/04/real-life-es6-arrow-fn/) @@ -66,6 +67,7 @@ - Jan Krems, [Generators Are Like Arrays](https://gist.github.com/jkrems/04a2b34fb9893e4c2b5c): 讨论Generator可以被当作数据结构看待 - Harold Cooper, [Coroutine Event Loops in Javascript](http://syzygy.st/javascript-coroutines/): Generator用于实现状态机 - Ruslan Ismagilov, [learn-generators](https://github.com/isRuslan/learn-generators): 编程练习,共6道题 +- Kyle Simpson, [Iterating ES6 Numbers](http://blog.getify.com/iterating-es6-numbers/): 在数值对象上部署遍历器 ## Promise对象 From b148bcdbfb89e6aa5aafd9e8b0469caf512f87da Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Mon, 9 Feb 2015 11:23:52 +0800 Subject: [PATCH 0030/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9string/template=20s?= =?UTF-8?q?tring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/reference.md | 2 ++ docs/string.md | 46 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index d89a6d9c1..6ff6dc055 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -42,6 +42,8 @@ - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 - Nicholas C. Zakas, [A critical review of ECMAScript 6 quasi-literals](http://www.nczonline.net/blog/2012/08/01/a-critical-review-of-ecmascript-6-quasi-literals/) - Mozilla Developer Network, [Template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings) +- Addy Osmani, [Getting Literal With ES6 Template Strings](http://updates.html5rocks.com/2015/01/ES6-Template-Strings): 模板字符串的介绍 +- Blake Winton, [ES6 Templates](https://weblog.latte.ca/blake/tech/firefox/templates.html): 模板字符串的介绍 ## Object diff --git a/docs/string.md b/docs/string.md index 3eb18afc6..40d7f0a79 100644 --- a/docs/string.md +++ b/docs/string.md @@ -375,13 +375,22 @@ r.sticky // true `In JavaScript this is not legal.` +console.log(`string text line 1 +string text line 2`); + // 字符串中嵌入变量 var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?` ``` -上面代码中的字符串,都是用反引号表示。如果在模板字符串中嵌入变量,需要将变量名写在`${}`之中。 +上面代码中的字符串,都是用反引号表示。如果在模板字符串中嵌入变量,需要将变量名写在`${}`之中。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。 + +```javascript + +var greeting = `\`Yo\` World!`; + +``` 大括号内部可以进行运算,以及引用对象属性。 @@ -402,6 +411,19 @@ console.log(`${obj.x + obj.y}`) ``` +模板字符串之中还能调用函数。 + +```javascript + +function fn() { + return "Hello World"; +} + +console.log(`foo ${fn()} bar`); +// foo Hello World bar + +``` + 如果模板字符串中的变量没有声明,将报错。 ```javascript @@ -441,21 +463,35 @@ tag`Hello ${ a + b } world ${ a * b}`; - 第二个参数: 15 - 第三个参数:50 +也就是说,tag函数实际的参数如下。 + +```javascript + +tag(['Hello ', ' world '], 15, 50) + +``` + +下面是tag函数的代码,以及运行结果。 + ```javascript var a = 5; var b = 10; function tag(s, v1, v2) { - console.log(s[0]); // "Hello " - console.log(s[1]); // " world " - console.log(v1); // 15 - console.log(v2); // 50 + console.log(s[0]); + console.log(s[1]); + console.log(v1); + console.log(v2); return "OK"; } tag`Hello ${ a + b } world ${ a * b}`; +// "Hello " +// " world " +// 15 +// 50 // "OK" ``` From 0c21b61c55374be2ca23fe4fbcc3624ea8ad81d9 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 10 Feb 2015 08:45:18 +0800 Subject: [PATCH 0031/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9promise/catch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/promise.md | 104 +++++++++++++++++++++++++++++++++++++++++++--- docs/reference.md | 1 + 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 640bfbcfb..da19ed131 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -96,7 +96,7 @@ var p2 = new Promise(function(resolve, reject){ 上面代码中,p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,这时p1的状态就会传递给p2。如果调用的时候,p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是fulfilled或者rejected,那么p2的回调函数将会立刻执行。 -## Promise.prototype.then方法:链式操作 +## Promise.prototype.then() Promise.prototype.then方法返回的是一个新的Promise对象,因此可以采用链式写法。 @@ -126,9 +126,9 @@ getJSON("/post/1.json").then(function(post) { 这种设计使得嵌套的异步操作,可以被很容易得改写,从回调函数的“横向发展”改为“向下发展”。 -## Promise.prototype.catch方法:捕捉错误 +## Promise.prototype.catch() -Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的别名,用于指定发生错误时的回调函数。 +Promise.prototype.catch方法是`Promise.prototype.then(null, rejection)`的别名,用于指定发生错误时的回调函数。 ```javascript @@ -141,6 +141,8 @@ getJSON("/posts.json").then(function(posts) { ``` +上面代码中,getJSON方法返回一个Promise对象,如果该对象运行正常,则会调用then方法指定的回调函数;如果该方法抛出错误,则会调用catch方法指定的回调函数,处理这个错误。 + Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。 ```javascript @@ -150,12 +152,102 @@ getJSON("/post/1.json").then(function(post) { }).then(function(comments) { // some code }).catch(function(error) { - // 处理前两个回调函数的错误 + // 处理前面三个Promise产生的错误 +}); + +``` + +上面代码中,一共有三个Promise对象:一个由getJSON产生,两个由then产生。它们之中任何一个抛出的错误,都会被最后一个catch捕获。 + +跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。 + +```javascript + +var someAsyncThing = function() { + return new Promise(function(resolve, reject) { + // 下面一行会报错,因为x没有声明 + resolve(x + 2); + }); +}; + +someAsyncThing().then(function() { + console.log('everything is great'); +}); + +``` + +上面代码中,someAsyncThing函数产生的Promise对象会报错,但是由于没有调用catch方法,这个错误不会被捕获,也不会传递到外层代码,导致运行后没有任何输出。 + +需要注意的是,catch方法返回的还是一个Promise对象,因此后面还可以接着调用then方法。 + +```javascript + +var someAsyncThing = function() { + return new Promise(function(resolve, reject) { + // 下面一行会报错,因为x没有声明 + resolve(x + 2); + }); +}; + +someAsyncThing().then(function() { + return someOtherAsyncThing(); +}).catch(function(error) { + console.log('oh no', error); +}).then(function() { + console.log('carry on'); }); +// oh no [ReferenceError: x is not defined] +// carry on ``` -## Promise.all方法,Promise.race方法 +上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。 + +catch方法之中,还能再抛出错误。 + +```javascript + +var someAsyncThing = function() { + return new Promise(function(resolve, reject) { + // 下面一行会报错,因为x没有声明 + resolve(x + 2); + }); +}; + +someAsyncThing().then(function() { + return someOtherAsyncThing(); +}).catch(function(error) { + console.log('oh no', error); + // 下面一行会报错,因为y没有声明 + y + 2; +}).then(function() { + console.log('carry on'); +}); +// oh no [ReferenceError: x is not defined] + +``` + +上面代码中,catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会到传递到外层。如果改写一下,结果就不一样了。 + +```javascript + +someAsyncThing().then(function() { + return someOtherAsyncThing(); +}).catch(function(error) { + console.log('oh no', error); + // 下面一行会报错,因为y没有声明 + y + 2; +}).catch(function(error) { + console.log('carry on', error); +}); +// oh no [ReferenceError: x is not defined] +// carry on [ReferenceError: y is not defined] + +``` + +上面代码中,第二个catch方法用来捕获,前一个catch方法抛出的错误。 + +## Promise.all(),Promise.race() Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。 @@ -202,7 +294,7 @@ var p = Promise.race([p1,p2,p3]); 如果Promise.all方法和Promise.race方法的参数,不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。 -## Promise.resolve方法,Promise.reject方法 +## Promise.resolve(),Promise.reject() 有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。 diff --git a/docs/reference.md b/docs/reference.md index 6ff6dc055..176562289 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -78,6 +78,7 @@ - Sandeep Panda, [An Overview of JavaScript Promises](http://www.sitepoint.com/overview-javascript-promises/): ES6 Promise入门介绍 - Jafar Husain, [Async Generators](https://docs.google.com/file/d/0B4PVbLpUIdzoMDR5dWstRllXblU/view?sle=true): 对async与Generator混合使用的一些讨论 - Axel Rauschmayer, [ECMAScript 6 promises (2/2): the API](http://www.2ality.com/2014/10/es6-promises-api.html): 对ES6 Promise规格和用法的详细介绍 +- Jack Franklin, [Embracing Promises in JavaScript](http://javascriptplayground.com/blog/2015/02/promises/): catch方法的例子 ## Class与模块 From 9344196b46bfdd0b26c69ffcf6ec740ddeda283c Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 10 Feb 2015 16:36:29 +0800 Subject: [PATCH 0032/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9iterator/Symbol.ite?= =?UTF-8?q?rator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/iterator.md | 8 ++++++++ docs/string.md | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/iterator.md b/docs/iterator.md index ebe3589f5..db07920bf 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -155,6 +155,14 @@ let obj = { ``` +对于类似数组的对象,部署Iterator接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数值的Iterator接口。 + +```javascript + +NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; + +``` + 如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。 ```javascript diff --git a/docs/string.md b/docs/string.md index 40d7f0a79..4d878b7d7 100644 --- a/docs/string.md +++ b/docs/string.md @@ -256,8 +256,8 @@ normalize方法可以接受四个参数。 ```javascript -'\u004F\u030C'.normalize(NFC).length // 1 -'\u004F\u030C'.normalize(NFD).length // 2 +'\u004F\u030C'.normalize('NFC').length // 1 +'\u004F\u030C'.normalize('NFD').length // 2 ``` From dce22909c47e7af21cbe59ec1d0fb5384e8ec38a Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 13 Feb 2015 12:27:50 +0800 Subject: [PATCH 0033/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9class/introduction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 83 +++++++++++++++++++++++++++++++++++++++++++---- docs/reference.md | 1 + 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/docs/class.md b/docs/class.md index 32a0af008..06eb934b1 100644 --- a/docs/class.md +++ b/docs/class.md @@ -17,7 +17,7 @@ Point.prototype.toString = function () { ``` -ES6引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。上面的代码用“类”改写,就是下面这样。 +ES6引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,class可以看作只是一个语法糖,没有引进任何ES5做不到的新功能,只是让对象原型的写法更加清晰而已。上面的代码用“类”改写,就是下面这样。 ```javascript @@ -35,14 +35,59 @@ class Point { } +``` + +上面代码定义了一个“类”,可以看到里面有一个constructor函数,这就是构造函数,而this关键字则代表实例对象。这个类除了构造方法,还定义了一个toString方法。注意,定义方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。 + +生成实例对象的写法,与ES5完全一样,也是使用new命令。 + +```javascript + var point = new Point(2,3); + point.toString() // (2, 3) +point.hasOwnProperty('x') // true +point.hasOwnProperty('y') // true +point.hasOwnProperty('toString') // false +point.__proto__.hasOwnProperty('toString') // false + ``` -上面代码定义了一个“类”,可以看到里面有一个constructor函数,这就是构造函数,而this关键字则代表实例对象。这个类除了构造方法,还定义了一个toString方法。注意,定义方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。 +上面代码中,x和y都是point自身的属性,所以hasOwnProperty方法返回true,而toString是原型对象的属性,所以hasOwnProperty方法返回false。这些都与ES5的行为保持一致。 + +```javascript + +var p1 = new Point(2,3); +var p2 = new Point(3,2); + +p1.__proto__ === p2.__proto__ +//true + +``` + +上面代码中,p1和p2都是Point的实例,它们的原型都是Point,所以\__proto__属性是相等的。 + +这也意味着,可以通过\__proto__属性为Class添加方法。 + +```javascript + +var p1 = new Point(2,3); +var p2 = new Point(3,2); -Class之间可以通过extends关键字,实现继承。 +p1.__proto__.printName = function () { return 'Oops' }; + +p1.printName() // "Oops" +p2.printName() // "Oops" + +var p3 = new Point(4,2); +p3.printName() // "Oops" + +``` + +上面代码在p1的原型上添加了一个printName方法,由于p1的原型就是p2的原型,因此p2也可以调用这个方法。而且,新建的实例p3也可以调用这个方法。这意味着,使用实例的\__proto__属性改写原型,必须相当谨慎,不推荐使用,因为这会不可逆转地改变Class。 + +Class之间可以通过extends关键字,实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。 ```javascript @@ -57,12 +102,12 @@ class ColorPoint extends Point {} class ColorPoint extends Point { constructor(x, y, color) { - super(x, y); // 等同于super.constructor(x, y) + super(x, y); // 等同于parent.constructor(x, y) this.color = color; } toString() { - return this.color+' '+super(); + return this.color+' '+super(); // 等同于parent.toString() } } @@ -71,7 +116,33 @@ class ColorPoint extends Point { 上面代码中,constructor方法和toString方法之中,都出现了super关键字,它指代父类的同名方法。在constructor方法内,super指代父类的constructor方法;在toString方法内,super指代父类的toString方法。 -有一个地方,需要注意。类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。 +父类和子类的\__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 + +``` + +通过子类的\__proto__属性,可以修改父类。 + +```javascript + +p2.__proto__.__proto__.printName = function () { + console.log('Ha'); +}; + +p1.printName() // Ha + +``` + +上面代码在ColorPoint的实例p2上向Point类添加方法,结果影响到了Point的实例p1。 + +有一个地方需要注意,类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。 ## Module的基本用法 diff --git a/docs/reference.md b/docs/reference.md index 176562289..d23dc9076 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -82,6 +82,7 @@ ## Class与模块 +- Sebastian Porto, [ES6 classes and JavaScript prototypes](https://reinteractive.net/posts/235-es6-classes-and-javascript-prototypes): ES6 Class的写法与ES5 Prototype的写法对比 - Jack Franklin, [An introduction to ES6 classes](http://javascriptplayground.com/blog/2014/07/introduction-to-es6-classes-tutorial/): ES6 class的入门介绍 - Jack Franklin, [JavaScript Modules the ES6 Way](http://24ways.org/2014/javascript-modules-the-es6-way/): ES6模块入门 - Axel Rauschmayer, [ECMAScript 6 modules: the final syntax](http://www.2ality.com/2014/09/es6-modules-final.html): ES6模块的介绍,以及与CommonJS规格的详细比较 From f6b5bf640bccfa2ebcb1a7d2a1f17d2ef2008a30 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 15 Feb 2015 08:24:27 +0800 Subject: [PATCH 0034/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9generator?= 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 5e493c74c..b8491b181 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -52,7 +52,7 @@ hw.next() 总结一下,Generator函数使用iterator接口,每次调用next方法的返回值,就是一个标准的iterator返回值:有着value和done两个属性的对象。其中,value是yield语句后面那个表达式的值,done是一个布尔值,表示是否遍历结束。 -由于Generator函数本身就能生成遍历器,所以它的Symbol.iterator属性指向自身。 +上一章说过,任意一个对象的Symbol.iterator属性,等于该对象的遍历器函数,即调用该函数会返回该对象的一个遍历器。由于Generator函数调用后返回自身的遍历器,所以Generator函数就是自身的遍历器函数,即它的Symbol.iterator属性指向自身。 ```javascript From 0d3a7ea26a796ccb1d6254128750487f8cfb207a Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 15 Feb 2015 19:13:15 +0800 Subject: [PATCH 0035/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9object/Symbol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/object.md | 263 ++++++++++++++++++++++++++++++++-------------- docs/reference.md | 3 +- 2 files changed, 186 insertions(+), 80 deletions(-) diff --git a/docs/object.md b/docs/object.md index 44ae792e3..f267bb0ba 100644 --- a/docs/object.md +++ b/docs/object.md @@ -349,18 +349,24 @@ Object.getPrototypeOf(obj) ## Symbol -ES6引入了一种新的原始数据类型Symbol,表示独一无二的ID。它通过Symbol函数生成。 +### 概述 + +在ES5中,对象的属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。 + +ES6引入了一种新的原始数据类型Symbol,表示独一无二的ID。它通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 ```javascript -let symbol1 = Symbol(); +let s = Symbol(); -typeof symbol +typeof s // "symbol" ``` -上面代码中,变量symbol1就是一个独一无二的ID。typeof运算符的结果,表明变量symbol1是Symbol数据类型,而不是字符串之类的其他类型。 +上面代码中,变量s就是一个独一无二的ID。typeof运算符的结果,表明变量s是Symbol数据类型,而不是字符串之类的其他类型。 + +注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。 Symbol函数可以接受一个字符串作为参数,表示Symbol实例的名称。 @@ -373,9 +379,27 @@ mySymbol.name ``` -上面代码表示,Symbol函数的字符串参数,用来指定生成的Symbol的名称,可以通过name属性读取。之所以要新增name属性,是因为键名是Symbol类型,而有些场合需要一个字符串类型的值来指代这个键。 +上面代码表示,Symbol函数的字符串参数,用来指定生成的Symbol的名称,可以通过name属性读取。之所以要新增name属性,是因为如果一个对象的属性名是Symbol类型,可能不太方便引用,而有些场合需要一个字符串类型的值来指代这个键。 -注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。 +Symbol函数的参数只是表示对当前Symbol类型的值的描述,因此相同参数的Symbol函数的返回值是不相等的。 + +```javascript + +// 没有参数的情况 +var s1 = Symbol(); +var s2 = Symbol(); + +s1 === s2 // false + +// 有参数的情况 +var s1 = Symbol("foo"); +var s2 = Symbol("foo"); + +s1 === s2 // false + +``` + +上面代码中,s1和s2都是Symbol函数的返回值,而且参数相同,但是它们是不相等的。 Symbol类型的值不能与其他类型的值进行运算,会报错。 @@ -399,34 +423,9 @@ sym.toString() ``` -symbol的最大特点,就是每一个Symbol都是不相等的,保证产生一个独一无二的值。 - -```javascript - -let w1 = Symbol(); -let w2 = Symbol(); -let w3 = Symbol(); - -w1 === w2 // false -w1 === w3 // false -w2 === w3 // false - -function f(w) { - switch (w) { - case w1: - ... - case w2: - ... - case w3: - ... - } -} - -``` - -上面代码中,w1、w2、w3三个变量都等于`Symbol()`,但是它们的值是不相等的。 +### 作为属性名的Symbol -由于这种特点,Symbol类型适合作为标识符,用于对象的属性名,保证了属性名之间不会发生冲突。如果一个对象由多个模块构成,这样就不会出现同名的属性,也就防止了键值被不小心改写或覆盖。Symbol类型还可以用于定义一组常量,防止它们的值发生冲突。 +Symbol类型作为标识符,用于对象的属性名时,保证了属性名之间不会发生冲突。如果一个对象由多个模块构成,这样就不会出现同名的属性,也就防止了键值被不小心改写或覆盖。Symbol类型还可以用于定义一组常量,防止它们的值发生冲突。 ```javascript @@ -445,11 +444,36 @@ var a = { var a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); +// 以上写法都得到同样结果 a[mySymbol] // "Hello!" ``` -上面代码通过方括号结构和Object.defineProperty两种方法,将对象的属性名指定为一个Symbol值。 +上面代码通过方括号结构和Object.defineProperty,将对象的属性名指定为一个Symbol值。 + +在对象内部使用Symbol属性名,必须采用属性名表达式,就像上面的第二种写法。 + +```javascript + +let s = Symbol(); + +let obj = { + [s]: function (arg) { ... } +}; + +obj[s](123); + +``` + +采用增强的对象写法,上面代码的obj对象可以写得更简洁一些。 + +```javascript + +let obj = { + [s](arg) { ... } +}; + +``` 注意,不能使用点结构,将Symbol值作为对象的属性名。 @@ -466,50 +490,63 @@ a[mySymbol] // undefined 上面代码中,mySymbol属性的值为未定义,原因在于`a.mySymbol`这样的写法,并不是把一个Symbol值当作属性名,而是把mySymbol这个字符串当作属性名进行赋值,这是因为点结构中的属性名永远都是字符串。 -下面的写法为Map结构添加了一个成员,但是该成员永远无法被引用。 +需要注意的是,Symbol类型作为属性名时,该属性还是公开属性,不是私有属性。 + +### Symbol.for(),Symbol.keyFor() + +Symbol.for方法在全局环境中搜索指定key的Symbol值,如果存在就返回这个Symbol值,否则就新建一个指定key的Symbol值并返回。 + +`Symbol.for()`与`Symbol()`这两种写法的区别是,前者会被登记在全局环境中供搜索,后者不会。`Symbol.for()`不会每次调用就返回一个新的Symbol类型的值,而是会先检查跟定的key是否已经存在,如果不存在才会新建一个值。 ```javascript -let a = Map(); -a.set(Symbol(), 'Noise'); -a.size // 1 +Symbol.for("bar") === Symbol.for("bar") +// true + +Symbol("bar") === Symbol("bar") +// false ``` -为Symbol函数添加一个参数,就可以引用了。 +上面代码中,由于`Symbol()`写法没有登记机制,所以每次调用都会返回一个不同的值。 + +Symbol.keyFor方法返回一个已登记的Symbol类型值的key。 ```javascript -let a = Map(); -a.set(Symbol('my_key'), 'Noise'); +var s1 = Symbol.for("foo"); +Symbol.keyFor(s1) // "foo" + +var s2 = Symbol("foo"); +Symbol.keyFor(s2) // undefined ``` -如果要在对象内部使用Symbol属性名,必须采用属性名表达式。 +上面代码中,变量s2属于未登记的Symbol值,所以返回undefined。 -```javascript +### 属性名的遍历 -let specialMethod = Symbol(); +Symbol作为属性名,该属性不会出现在for...in循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回,但是有一个对应的Object.getOwnPropertySymbols方法,以及Object.getOwnPropertyKeys方法都可以获取指定对象的所有Symbol属性名。 -let obj = { - [specialMethod]: function (arg) { ... } -}; +Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。 -obj[specialMethod](123); +```javascript -``` +var obj = {}; +var a = Symbol('a'); +var b = Symbol.for('b'); -采用增强的对象写法,上面代码的obj对象可以写得更简洁一些。 +obj[a] = 'Hello'; +obj[b] = 'World'; -```javascript +var objectSymbols = Object.getOwnPropertySymbols(obj); -let obj = { - [specialMethod](arg) { ... } -}; +objectSymbols +// [Symbol(a), Symbol(b)] ``` -Symbol类型作为属性名,不会出现在for...in循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回,但是有一个对应的Object.getOwnPropertySymbols方法,以及Object.getOwnPropertyKeys方法都可以获取Symbol属性名。 +下面是另一个例子,Object.getOwnPropertySymbols方法与for...in循环、Object.getOwnPropertyNames方法进行对比的例子。 ```javascript @@ -518,9 +555,13 @@ var obj = {}; var foo = Symbol("foo"); Object.defineProperty(obj, foo, { - value: "foobar", + value: "foobar", }); +for (var i in obj) { + console.log(i); // 无输出 +} + Object.getOwnPropertyNames(obj) // [] @@ -531,7 +572,7 @@ Object.getOwnPropertySymbols(obj) 上面代码中,使用Object.getOwnPropertyNames方法得不到Symbol属性名,需要使用Object.getOwnPropertySymbols方法。 -Reflect.ownKeys方法返回所有类型的键名。 +Reflect.ownKeys方法可以返回所有类型的键名。 ```javascript @@ -546,8 +587,42 @@ Reflect.ownKeys(obj) ``` +### 内置的Symbol值 + +除了定义自己使用的Symbol值以外,ES6还提供一些内置的Symbol值,指向语言内部使用的方法。 + +(1)Symbol.hasInstance + +该值指向对象的内部方法@@hasInstance(两个@表示这是内部方法,外部无法直接调用,下同),该对象使用instanceof运算符时,会调用这个方法,判断该对象是否为某个构造函数的实例。 + +(2)Symbol.isConcatSpreadable + +该值指向对象的内部方法@@isConcatSpreadable,该对象使用Array.prototype.concat()时,会调用这个方法,返回一个布尔值,表示该对象是否可以扩展成数组。 + +(3)Symbol.isRegExp + +该值指向对象的内部方法@@isRegExp,该对象被用作正则表达式时,会调用这个方法,返回一个布尔值,表示该对象是否为一个正则对象。 + +(4)Symbol.iterator + +该值指向对象的内部方法@@iterator,该对象进行for...of循环时,会调用这个方法,返回该对象的默认遍历器,详细介绍参见《Iterator和for...of循环》一章。 + +(5)Symbol.toPrimitive + +该值指向对象的内部方法@@toPrimitive,该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 + +(6)Symbol.toStringTag + +该值指向对象的内部属性@@toStringTag,在该对象上调用Object.prototype.toString()时,会返回这个属性,它是一个字符串,表示该对象的字符串形式。 + +(7)Symbol.unscopables + +该值指向对象的内部属性@@unscopables,返回一个数组,成员为该对象使用with关键字时,会被with环境排除在的那些属性值。 + ## Proxy +### 概述 + Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。 Proxy可以理解成在目标对象之前,架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作。 @@ -592,7 +667,29 @@ obj.time // 35 对于没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。 -下面是另一个拦截读取操作的例子。 +Proxy支持的拦截操作一览。 + +- defineProperty(target, propKey, propDesc):返回一个布尔值,拦截Object.defineProperty(proxy, propKey, propDesc) +- deleteProperty(target, propKey) :返回一个布尔值,拦截delete proxy[propKey] +- enumerate(target):返回一个遍历器,拦截for (x in proxy) +- get(target, propKey, receiver):返回类型不限,拦截对象属性的读取 +- getOwnPropertyDescriptor(target, propKey) :返回属性的描述对象,拦截Object.getOwnPropertyDescriptor(proxy, propKey) +- getPrototypeOf(target) :返回一个对象,拦截Object.getPrototypeOf(proxy) +- has(target, propKey):返回一个布尔值,拦截propKey in proxy +- isExtensible(target):返回一个布尔值,拦截Object.isExtensible(proxy) +- ownKeys(target):返回一个数组,拦截Object.getOwnPropertyPropertyNames(proxy)、Object.getOwnPropertyPropertySymbols(proxy)、Object.keys(proxy) +- preventExtensions(target):返回一个布尔值,拦截Object.preventExtensions(proxy) +- set(target, propKey, value, receiver):返回一个布尔值,拦截对象属性的设置 +- setPrototypeOf(target, proto):返回一个布尔值,拦截Object.setPrototypeOf(proxy, proto) + +如果目标对象是函数,那么还有两种额外操作可以拦截。 + +- apply方法:拦截Proxy实例作为函数调用的操作,比如proxy(···)、proxy.call(···)、proxy.apply(···)。 +- construct方法:拦截Proxy实例作为构造函数调用的操作,比如new proxy(···)。 + +### get + +get方法用于拦截某个属性的读取操作。上文已经有一个例子,下面是另一个拦截读取操作的例子。 ```javascript @@ -650,7 +747,9 @@ pipe(3) . double . pow . reverseInt . get 上面代码设置Proxy以后,达到了将函数名链式使用的效果。 -除了取值函数get,Proxy还可以设置存值函数set,用来拦截某个属性的赋值行为。假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性值符合要求。 +### set + +set方法用来拦截某个属性的赋值操作。假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性值符合要求。 ```javascript @@ -682,6 +781,30 @@ person.age = 300 // 报错 上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新DOM。 +### apply + +apply方法拦截函数的调用、call和apply操作。 + +```javascript + +var target = function () { return 'I am the target'; }; +var handler = { + apply: function (receiver, ...args) { + return 'I am the proxy'; + } +}; + +var p = new Proxy(target, handler); + +p() === 'I am the proxy'; +// true + +``` + +上面代码中,变量p是Proxy的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。 + +### ownKeys + ownKeys方法用来拦截Object.keys()操作。 ```javascript @@ -703,25 +826,7 @@ Object.keys(proxy) 上面代码拦截了对于target对象的Object.keys()操作,返回预先设定的数组。 -Proxy支持的拦截操作一览。 - -- defineProperty(target, propKey, propDesc):返回一个布尔值,拦截Object.defineProperty(proxy, propKey, propDesc) -- deleteProperty(target, propKey) :返回一个布尔值,拦截delete proxy[propKey] -- enumerate(target):返回一个遍历器,拦截for (x in proxy) -- get(target, propKey, receiver):返回类型不限,拦截对象属性的读取 -- getOwnPropertyDescriptor(target, propKey) :返回属性的描述对象,拦截Object.getOwnPropertyDescriptor(proxy, propKey) -- getPrototypeOf(target) :返回一个对象,拦截Object.getPrototypeOf(proxy) -- has(target, propKey):返回一个布尔值,拦截propKey in proxy -- isExtensible(target):返回一个布尔值,拦截Object.isExtensible(proxy) -- ownKeys(target):返回一个数组,拦截Object.getOwnPropertyPropertyNames(proxy)、Object.getOwnPropertyPropertySymbols(proxy)、Object.keys(proxy) -- preventExtensions(target):返回一个布尔值,拦截Object.preventExtensions(proxy) -- set(target, propKey, value, receiver):返回一个布尔值,拦截对象属性的设置 -- setPrototypeOf(target, proto):返回一个布尔值,拦截Object.setPrototypeOf(proxy, proto) - -如果目标对象是函数,那么还有两种额外操作可以拦截。 - -- apply方法:拦截Proxy实例作为函数调用的操作,比如proxy(···)、proxy.call(···)、proxy.apply(···)。 -- construct方法:拦截Proxy实例作为构造函数调用的操作,比如new proxy(···)。 +### Proxy.revocable() Proxy.revocable方法返回一个可取消的Proxy实例。 @@ -731,7 +836,7 @@ let target = {}; let handler = {}; let {proxy, revoke} = Proxy.revocable(target, handler); - + proxy.foo = 123; proxy.foo // 123 diff --git a/docs/reference.md b/docs/reference.md index d23dc9076..2ae84e0d0 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -52,7 +52,8 @@ - Sella Rafaeli, [Native JavaScript Data-Binding](http://www.sellarafaeli.com/blog/native_javascript_data_binding): 如何使用Object.observe方法,实现数据对象与DOM对象的双向绑定 - Axel Rauschmayer, [Symbols in ECMAScript 6](http://www.2ality.com/2014/12/es6-symbols.html): Symbol简介 - Axel Rauschmayer, [Meta programming with ECMAScript 6 proxies](http://www.2ality.com/2014/12/es6-proxies.html): Proxy详解 -- Daniel Zautner, [Meta-programming JavaScript Using Proxies](http://dzautner.com/meta-programming-javascript-using-proxies/):使用Proxy实现元编程 +- Daniel Zautner, [Meta-programming JavaScript Using Proxies](http://dzautner.com/meta-programming-javascript-using-proxies/): 使用Proxy实现元编程 +- MDN, [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol): Symbol类型的详细介绍 ## Iterator和Generator From 4c9b971e570da41d0918cf2c0931640133fe0313 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 15 Feb 2015 22:02:35 +0800 Subject: [PATCH 0036/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9class/extends?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 17 +++++++++++++++++ docs/reference.md | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/class.md b/docs/class.md index 06eb934b1..f8585835f 100644 --- a/docs/class.md +++ b/docs/class.md @@ -142,6 +142,23 @@ p1.printName() // Ha 上面代码在ColorPoint的实例p2上向Point类添加方法,结果影响到了Point的实例p1。 +下面是一个继承原生的Array构造函数的例子。 + +```javascript + +class MyArray extends Array { + constructor(...args) { + super(...args); + } +} + +var arr = new MyArray(); +arr[1] = 12; + +``` + +上面代码定义了一个MyArray的类,继承了Array构造函数。因此,就可以从MyArray生成数组的实例。 + 有一个地方需要注意,类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。 ## Module的基本用法 diff --git a/docs/reference.md b/docs/reference.md index 2ae84e0d0..9cd6fc315 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -16,7 +16,7 @@ - Nicholas C. Zakas, [Understanding ECMAScript 6](https://github.com/nzakas/understandinges6) - Justin Drake, [ECMAScript 6 in Node.JS](https://github.com/JustinDrake/node-es6-examples) - Ryan Dao, [Summary of ECMAScript 6 major features](http://ryandao.net/portal/content/summary-ecmascript-6-major-features) -- Luke Hoban, [ES6 features](https://github.com/lukehoban/es6features) +- Luke Hoban, [ES6 features](https://github.com/lukehoban/es6features): ES6新语法点的罗列 - Traceur-compiler, [Language Features](https://github.com/google/traceur-compiler/wiki/LanguageFeatures): Traceur文档列出的一些ES6例子 - Axel Rauschmayer, [ECMAScript 6: what’s next for JavaScript?](https://speakerdeck.com/rauschma/ecmascript-6-whats-next-for-javascript-august-2014): 关于ES6新增语法的综合介绍,有很多例子 - Toby Ho, [ES6 in io.js](http://davidwalsh.name/es6-io) From 1f7cb6cf7a83b3ad37ef014b72759c7f4d0b9782 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 15 Feb 2015 23:50:29 +0800 Subject: [PATCH 0037/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9weakmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/set-map.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/set-map.md b/docs/set-map.md index 51023e454..5fa928e95 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -644,18 +644,16 @@ var wm = new WeakMap(); var element = document.querySelector(".element"); wm.set(element, "Original"); - -var value = wm.get(element); -console.log(value); // "Original" +wm.get(element) // "Original" element.parentNode.removeChild(element); element = null; - -value = wm.get(element); -console.log(value); // undefined +wm.get(element) // undefined ``` +上面代码中,变量wm是一个WeakMap实例,我们将一个DOM节点element作为键名,然后销毁这个节点,element对应的键就自动消失了,再引用这个键名就返回undefined。 + WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有key()、values()和entries()方法),也没有size属性;二是无法清空,即不支持clear方法。这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。 ```javascript @@ -670,7 +668,26 @@ wm.forEach ``` -WeakMap的一个用处是部署私有属性。 +前文说过,WeakMap应用的典型场合就是DOM节点作为键名。下面是一个例子。 + +```javascript + +let myElement = document.getElementById('logo'); +let myWeakmap = new WeakMap(); + +myWeakmap.set(myElement, {timesClicked: 0}); + +myElement.addEventListener('click', function() { + let logoData = myWeakmap.get(myElement); + logoData.timesClicked++; + myWeakmap.set(myElement, logoData); +}, false); + +``` + +上面代码中,myElement是一个DOM节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在WeakMap里,对应的键名就是myElement。一旦这个DOM节点删除,该状态就会自动消失,不存在内存泄漏风险。 + +WeakMap的另一个用处是部署私有属性。 ```javascript From 4ee9fa07d9aaefa5dbf4e0a70ec9b0594a43737f Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Mon, 16 Feb 2015 17:08:54 +0800 Subject: [PATCH 0038/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 369 ++++++++++++++++++++++++++++++++++++++++++++-- docs/reference.md | 2 + 2 files changed, 356 insertions(+), 15 deletions(-) diff --git a/docs/class.md b/docs/class.md index f8585835f..5dd9cf410 100644 --- a/docs/class.md +++ b/docs/class.md @@ -2,6 +2,10 @@ ## Class +### 基本语法 + +**(1)概述** + ES5通过构造函数,定义并生成新对象。下面是一个例子。 ```javascript @@ -17,7 +21,7 @@ Point.prototype.toString = function () { ``` -ES6引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,class可以看作只是一个语法糖,没有引进任何ES5做不到的新功能,只是让对象原型的写法更加清晰而已。上面的代码用“类”改写,就是下面这样。 +ES6引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰而已。上面的代码用“类”改写,就是下面这样。 ```javascript @@ -37,13 +41,68 @@ class Point { ``` -上面代码定义了一个“类”,可以看到里面有一个constructor函数,这就是构造函数,而this关键字则代表实例对象。这个类除了构造方法,还定义了一个toString方法。注意,定义方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。 +上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。Point类除了构造方法,还定义了一个toString方法。注意,定义方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。 + +**(2)constructor方法** + +constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,该方法会被默认添加,代码如下。 + +```javascript + +constructor() {} -生成实例对象的写法,与ES5完全一样,也是使用new命令。 +``` + +constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。 ```javascript -var point = new Point(2,3); +class Foo { + constructor() { + return Object.create(null); + } +} + +new Foo() instanceof Foo +// false + +``` + +上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。 + +**(3)实例对象** + +生成实例对象的写法,与ES5完全一样,也是使用new命令。如果忘记加上new,像函数那样调用Class,将会报错。 + +```javascript + +// 报错 +var point = Point(2, 3); + +// 正确 +var point = new Point(2, 3); + +``` + +与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。 + +```javascript + +//定义类 +class Point { + + constructor(x, y) { + this.x = x; + this.y = y; + } + + toString() { + return '('+this.x+', '+this.y+')'; + } + +} + +var point = Point(2, 3); point.toString() // (2, 3) @@ -54,7 +113,7 @@ point.__proto__.hasOwnProperty('toString') // false ``` -上面代码中,x和y都是point自身的属性,所以hasOwnProperty方法返回true,而toString是原型对象的属性,所以hasOwnProperty方法返回false。这些都与ES5的行为保持一致。 +上面代码中,x和y都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回false。这些都与ES5的行为保持一致。 ```javascript @@ -85,7 +144,77 @@ p3.printName() // "Oops" ``` -上面代码在p1的原型上添加了一个printName方法,由于p1的原型就是p2的原型,因此p2也可以调用这个方法。而且,新建的实例p3也可以调用这个方法。这意味着,使用实例的\__proto__属性改写原型,必须相当谨慎,不推荐使用,因为这会不可逆转地改变Class。 +上面代码在p1的原型上添加了一个printName方法,由于p1的原型就是p2的原型,因此p2也可以调用这个方法。而且,此后新建的实例p3也可以调用这个方法。这意味着,使用实例的\__proto\__属性改写原型,必须相当谨慎,不推荐使用,因为这会改变Class的原始定义,影响到所有实例。 + +**(4)name属性** + +由于本质上,ES6的Class只是ES5的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。 + +```javascript + +class Point {} + +Point.name // "Point" + +``` + +name属性总是返回紧跟在class关键字后面的类名。 + +**(5)Class表达式** + +与函数一样,Class也可以使用表达式的形式定义。 + +```javascript + +const MyClass = class Me { + getClassName() { + return Me.name; + } +}; + +``` + +上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass而不是Me,Me只在Class的内部代码可用,指代当前类。 + +```javascript + +let inst = new MyClass(); +inst.getClassName() // Me +Me.name // ReferenceError: Me is not defined + +``` + +上面代码表示,Me只在Class内部有定义。 + +如果Class内部没用到的话,可以省略Me,也就是可以写成下面的形式。 + +```javascript + +const MyClass = class { /* ... */ }; + +``` + +**(6)不存在变量提升** + +Class不存在变量提升(hoist),这一点与ES5完全不同。 + +```javascript + +new Foo(); // ReferenceError + +class Foo {} + +``` + +上面代码中,Foo类使用在前,定义在后,这样会报错,因为ES6不会把变量声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。 + +**(7)严格模式** + +类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。 + +### Class的继承 + +**(1)基本用法** Class之间可以通过extends关键字,实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。 @@ -116,19 +245,121 @@ class ColorPoint extends Point { 上面代码中,constructor方法和toString方法之中,都出现了super关键字,它指代父类的同名方法。在constructor方法内,super指代父类的constructor方法;在toString方法内,super指代父类的toString方法。 -父类和子类的\__proto__属性,指向是不一样的。 +子类必须在constructor方法中调用super方法,否则新建实例时会报错。 ```javascript -var p1 = new Point(2,3); -var p2 = new ColorPoint(2,3,red); +class Point { /* ... */ } + +class ColorPoint extends Foo { + constructor() { + } +} + +let cp = new ColorPoint(); // ReferenceError + +``` + +如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。 + +```javascript + +constructor(...args) { + super(...args); +} + +``` + +另一个需要注意的地方是,只有调用super方法之后,才可以使用this关键字,否则会报错。 + +```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的行为完全一致。 + +**(2)prototype属性** + +Class作为构造函数的升级,也有自己的prototype属性,其规则与ES5构造函数的prototype属性一致。 + +```javascript + +class A { +} + +A.prototype === Function.prototype +// true + +class B extends A { +} + +B.prototype === C +// true + +class C extends Object { +} + +C.prototype === Object +// true + +``` + +上面代码中,子类的prototype属性都指向父类。如果一个类是基类(即不存在任何继承),那么它的原型指向`Function.prototype`。 + +**(3)Object.getPrototypeOf方法** + +Object.getPrototypeOf方法可以用来从子类上获取父类。 + +```javascript + +Object.getPrototypeOf(ColorPoint) === Point +// true + +``` + +**(4)\__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 ``` -通过子类的\__proto__属性,可以修改父类。 +通过子类的\__proto\__属性,可以修改父类。 ```javascript @@ -136,12 +367,14 @@ p2.__proto__.__proto__.printName = function () { console.log('Ha'); }; -p1.printName() // Ha +p1.printName() // "Ha" ``` 上面代码在ColorPoint的实例p2上向Point类添加方法,结果影响到了Point的实例p1。 +**(5)构造函数的继承** + 下面是一个继承原生的Array构造函数的例子。 ```javascript @@ -157,13 +390,119 @@ arr[1] = 12; ``` -上面代码定义了一个MyArray的类,继承了Array构造函数。因此,就可以从MyArray生成数组的实例。 +上面代码定义了一个MyArray类,继承了Array构造函数,因此就可以从MyArray生成数组的实例。这意味着,ES6可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。 + +上面这个例子也说明,extends关键字不仅可以用来继承类,还可以用来继承构造函数。下面是一个自定义Error子类的例子。 + +```javascript + +class MyError extends Error { +} + +throw new MyError('Something happened!'); + +``` + +### 取值函数getter和存值函数setter + +与ES5一样,在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数。 + +```javascript + +class MyClass { + get prop() { + return 'getter'; + } + set prop(value) { + console.log('setter: '+value); + } +} + +let inst = new MyClass(); + +inst.prop = 123; +// setter: 123 + +inst.prop +// 'getter' + +``` + +上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。 + +### 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循环会自动调用这个遍历器。 + +### 静态方法 + +类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。 + +```javascript + +class Foo { + static classMethod() { + return 'hello'; + } +} + +Foo.classMethod() // 'hello' + +var foo = new Foo(); +foo.classMethod() +// TypeError: undefined 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可以调用这个方法。 -有一个地方需要注意,类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。 +## Module -## Module的基本用法 +ES6的Class只是面向对象编程的语法糖,升级了ES5的对象定义的写法,并没有解决模块化问题。Module功能就是为了解决这个问题而提出的。 -JavaScript没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby的require、Python的import,甚至就连CSS都有@import,但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。 +历史上,JavaScript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby的require、Python的import,甚至就连CSS都有@import,但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。 在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。 diff --git a/docs/reference.md b/docs/reference.md index 9cd6fc315..b5b7797ff 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -34,6 +34,7 @@ - Axel Rauschmayer, [ECMAScript 6’s new array methods](http://www.2ality.com/2014/05/es6-array-methods.html): 对ES6新增的数组方法的全面介绍 - Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 +- Dwayne Charrington, [What Are Weakmaps In ES6?](http://ilikekillnerds.com/2015/02/what-are-weakmaps-in-es6/): WeakMap数据结构介绍 - Axel Rauschmayer, [ECMAScript 6: maps and sets](http://www.2ality.com/2015/01/es6-maps-sets.html): Set和Map结构的详细介绍 - Ragan Wald, [Destructuring and Recursion in ES6](http://raganwald.com/2015/02/02/destructuring.html): rest参数和扩展运算符的详细介绍 @@ -89,6 +90,7 @@ - Axel Rauschmayer, [ECMAScript 6 modules: the final syntax](http://www.2ality.com/2014/09/es6-modules-final.html): ES6模块的介绍,以及与CommonJS规格的详细比较 - Dave Herman, [Static module resolution](http://calculist.org/blog/2012/06/29/static-module-resolution/): ES6模块的静态化设计思想 - Axel Rauschmayer, [ECMAScript 6: new OOP features besides classes](http://www.2ality.com/2014/12/es6-oop.html) +- Axel Rauschmayer, [Classes in ECMAScript 6 (final semantics)](http://www.2ality.com/2015/02/es6-classes-final.html): Class语法的详细介绍和设计思想分析 ## 工具 From caacc57d9f519092d86db9250e756bab30a318f2 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Mon, 16 Feb 2015 18:47:46 +0800 Subject: [PATCH 0039/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index b8491b181..b329c11a2 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -754,9 +754,12 @@ function* longRunningTask() { scheduler(longRunningTask()); function scheduler(task) { - setTimeout(function () { - if (!task.next(task.value).done) { - scheduler(task); + setTimeout(function() { + var taskObj = task.next(task.value); + // 如果Generator函数未结束,就继续调用 + if (!taskObj.done) { + task.value = taskObj.value + scheduler(task); } }, 0); } From b88e6b611e194846eaca9f07120f19997ef52af7 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Mon, 16 Feb 2015 22:27:58 +0800 Subject: [PATCH 0040/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9promise/async?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/promise.md | 128 ++++++++++++++++++++++++++++++++++++++++++++-- docs/reference.md | 1 + 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index da19ed131..2bd73e70f 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -383,9 +383,13 @@ run(g); ## async函数 -async函数是用来取代回调函数的另一种方法。 +### 概述 -只要函数名之前加上async关键字,就表明该函数内部有异步操作。该异步操作应该返回一个Promise对象,前面用await关键字注明。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。 +async函数与Promise、Generator函数一样,是用来取代回调函数、解决异步操作的另一种方法。它可以写出比Promise和Generator更简洁易读的代码,但是依赖这两者来实现。 + +async函数并不属于ES6,而是被列入了ES7,但是traceur编译器和regenerator转码器已经实现了这个功能。 + +在用法上,只要函数名之前加上async关键字,就表明该函数内部有异步操作。该异步操作应该返回一个Promise对象,前面用await关键字注明。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。 ```javascript @@ -417,4 +421,122 @@ async function asyncValue(value) { 上面代码中,asyncValue函数前面有async关键字,表明函数体内有异步操作。执行的时候,遇到await语句就会先返回,等到timeout函数执行完毕,再返回value。 -async函数并不属于ES6,而是被列入了ES7,但是traceur编译器已经实现了这个功能。 +### 与Promise、Generator的比较 + +我们通过一个例子,来看Async函数与Promise、Generator函数的区别。 + +假定某个DOM元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。 + +首先是Promise的写法。 + +```javascript + +function chainAnimationsPromise(elem, animations) { + + // 变量ret用来保存上一个动画的返回值 + var ret = null; + + // 新建一个空的Promise + var p = Promise.resolve(); + + // 使用then方法,添加所有动画 + for(var anim in animations) { + p = p.then(function(val) { + ret = val; + return anim(elem); + }) + } + + // 返回一个部署了错误捕捉机制的Promise + return p.catch(function(e) { + /* 忽略错误,继续执行 */ + }).then(function() { + return ret; + }); + +} + +``` + +虽然Promise的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是Promise的API(then、catch等等),操作本身的语义反而不容易看出来。 + +接着是Generator函数的写法。 + +```javascript + +function chainAnimationsGenerator(elem, animations) { + + return spawn(function*() { + var ret = null; + try { + for(var anim of animations) { + ret = yield anim(elem); + } + } catch(e) { + /* 忽略错误,继续执行 */ + } + return ret; + }); + +} + +``` + +上面代码使用Generator函数遍历了每个动画,语义比Promise写法更清晰,用户定义的操作全部都出现在spawn函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行Generator函数,上面代码的spawn函数就是任务运行器,它返回一个Promise对象,而且必须保证yield语句后面的表达式,必须返回一个Promise。下面是spawn函数的代码。 + +```javascript + +function spawn(genF) { + // 返回一个Promise + return new Promise(function(resolve, reject) { + // 执行Generator函数,返回一个遍历器 + var gen = genF(); + + // 定义一个函数,执行每一个任务 + function step(nextF) { + var next; + try { + next = nextF(); + } catch(e) { + // 如果任务执行出错,Promise状态变为已失败 + reject(e); + return; + } + if(next.done) { + // 所有任务执行完毕,Promise状态变为已完成 + resolve(next.value); + return; + } + // 如果还有下一个任务,就继续调用step方法 + Promise.resolve(next.value).then(function(v) { + step(function() { return gen.next(v); }); + }, function(e) { + step(function() { return gen.throw(e); }); + }); + } + + step(function() { return gen.next(undefined); }); + }); +} + +``` + +最后是Async函数的写法。 + +```javascript + +async function chainAnimationsAsync(elem, animations) { + var ret = null; + try { + for(var anim of animations) { + ret = await anim(elem); + } + } catch(e) { + /* 忽略错误,继续执行 */ + } + return ret; +} + +``` + +可以看到Async函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它实际上将Generator写法中的任务运行器,改在语言层面提供,因此代码量最少。Generator写法的spawn函数本质是将Generator函数转为Promise对象,Async函数将这个过程在语言内部处理掉了,不暴露给用户。 diff --git a/docs/reference.md b/docs/reference.md index b5b7797ff..165cbf7e6 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -81,6 +81,7 @@ - Jafar Husain, [Async Generators](https://docs.google.com/file/d/0B4PVbLpUIdzoMDR5dWstRllXblU/view?sle=true): 对async与Generator混合使用的一些讨论 - Axel Rauschmayer, [ECMAScript 6 promises (2/2): the API](http://www.2ality.com/2014/10/es6-promises-api.html): 对ES6 Promise规格和用法的详细介绍 - Jack Franklin, [Embracing Promises in JavaScript](http://javascriptplayground.com/blog/2015/02/promises/): catch方法的例子 +- Luke Hoban, [Async Functions for ECMAScript](https://github.com/lukehoban/ecmascript-asyncawait): Async函数的设计思想,与Promise、Gernerator函数的关系 ## Class与模块 From 0f6ed36b02363cd8c8ee82406a5387c6c5630b6e Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 17 Feb 2015 08:00:10 +0800 Subject: [PATCH 0041/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9desctructuring?= =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/destructuring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/destructuring.md b/docs/destructuring.md index 9066e9388..538835562 100644 --- a/docs/destructuring.md +++ b/docs/destructuring.md @@ -284,7 +284,7 @@ jQuery.ajax = function (url, { **(5)遍历Map结构** -任何部署了Iterator接口的对象,都可以用for...of循环遍历。Map结构原生支持Iterator接口,配合变量的结构赋值,获取键名和键值就非常方便。 +任何部署了Iterator接口的对象,都可以用for...of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。 ```javascript From 1f3489eab8933e9e57053b19c84cd86f3495611c Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 17 Feb 2015 08:55:52 +0800 Subject: [PATCH 0042/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9promise/catch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/promise.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/promise.md b/docs/promise.md index 2bd73e70f..a0779c482 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -143,6 +143,37 @@ getJSON("/posts.json").then(function(posts) { 上面代码中,getJSON方法返回一个Promise对象,如果该对象运行正常,则会调用then方法指定的回调函数;如果该方法抛出错误,则会调用catch方法指定的回调函数,处理这个错误。 +下面是一个例子。 + +```javascript + +var promise = new Promise(function(resolve, reject) { + throw new Error('test') +}); +promise.catch(function(error) { console.log(error) }); +// Error: test + +``` + +上面代码中,Promise抛出一个错误,就被catch方法指定的回调函数捕获。 + +如果Promise状态已经变成“已完成”,再抛出错误是无效的。 + +```javascript + +var promise = new Promise(function(resolve, reject) { + resolve("ok"); + throw new Error('test'); +}); +promise + .then(function(value) { console.log(value) }) + .catch(function(error) { console.log(error) }); +// ok + +``` + +上面代码中,Promise在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。 + Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。 ```javascript @@ -178,6 +209,20 @@ someAsyncThing().then(function() { 上面代码中,someAsyncThing函数产生的Promise对象会报错,但是由于没有调用catch方法,这个错误不会被捕获,也不会传递到外层代码,导致运行后没有任何输出。 +```javascript + +var promise = new Promise(function(resolve, reject) { + resolve("ok"); + setTimeout(function() { throw new Error('test') }, 0) +}); +promise.then(function(value) { console.log(value) }); +// ok +// Uncaught Error: test + +``` + +上面代码中,Promise指定在下一轮“事件循环”再抛出错误,结果由于没有指定catch语句,就冒泡到最外层,成了未捕获的错误。 + 需要注意的是,catch方法返回的还是一个Promise对象,因此后面还可以接着调用then方法。 ```javascript From b5253b24beb85f86c3ee0636adb12c26e4482af9 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 17 Feb 2015 13:40:43 +0800 Subject: [PATCH 0043/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9let?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 12 +++++++ docs/let.md | 88 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/docs/class.md b/docs/class.md index 5dd9cf410..fb91cb0d1 100644 --- a/docs/class.md +++ b/docs/class.md @@ -208,6 +208,18 @@ class Foo {} 上面代码中,Foo类使用在前,定义在后,这样会报错,因为ES6不会把变量声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。 +```javascript + +{ + let Foo = class {}; + class Bar extends Foo { + } +} + +``` + +如果存在Class的提升,上面代码将报错,因为let命令也是不提升的。 + **(7)严格模式** 类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。 diff --git a/docs/let.md b/docs/let.md index 9cd7e0adc..c866143a3 100644 --- a/docs/let.md +++ b/docs/let.md @@ -2,17 +2,19 @@ ## let命令 +### 基本用法 + ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。 ```javascript { - let a = 10; - var b = 1; + let a = 10; + var b = 1; } a // ReferenceError: a is not defined. -b //1 +b // 1 ``` @@ -32,18 +34,17 @@ console.log(i) 上面代码的计数器i,只在for循环体内有效。 -下面的代码如果使用var,最后输出的是9。 +下面的代码如果使用var,最后输出的是10。 ```javascript var a = []; for (var i = 0; i < 10; i++) { - var c = i; a[i] = function () { - console.log(c); + console.log(i); }; } -a[6](); // 9 +a[6](); // 10 ``` @@ -52,16 +53,17 @@ a[6](); // 9 ```javascript var a = []; -for (var i = 0; i < 10; i++) { - let c = i; +for (let i = 0; i < 10; i++) { a[i] = function () { - console.log(c); + console.log(i); }; } a[6](); // 6 ``` +### 不存在变量提升 + let不像var那样,会发生“变量提升”现象。 ```javascript @@ -88,6 +90,21 @@ if (1) { 上面代码中,由于块级作用域内typeof运行时,x还没有声明,所以会抛出一个ReferenceError。 +只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。 + +```javascript + +var tmp = 123; + +if (true) { + tmp = 'abc'; // ReferenceError + let tmp; +} + +``` + +上面代码中,存在全局量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。 + 总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。 ```javascript @@ -112,16 +129,14 @@ if (true) { ```javascript -function bar(x=y, y=2) { - return [x, y]; -} - -bar(); // 报错 +// 报错 ``` 上面代码中,调用bar函数之所以报错,是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。 +需要注意的是,函数参数的作用域与函数体的作用域是分离的。 + ```javascript let foo = 'outer'; @@ -135,9 +150,11 @@ bar(); ``` -上面代码中,函数bar的参数func,默认是一个匿名函数,返回值为foo。这个匿名函数运行时,foo只在函数体外声明,内层的声明还没执行,因此foo指向函数体外的声明,输出outer。 +上面代码中,函数bar的参数func,默认是一个匿名函数,返回值为变量foo。这个匿名函数运行时,foo只在函数体外声明,内层的声明还没执行,因此foo指向函数体外的声明,输出outer。 + +### 不允许重复声明 -注意,let不允许在相同作用域内,重复声明同一个变量。 +let不允许在相同作用域内,重复声明同一个变量。 ```javascript @@ -250,14 +267,27 @@ const的作用域与let命令相同:只在声明所在的块级作用域内有 ```javascript -if (condition) { - const MAX = 5; +if (true) { + const MAX = 5; } // 常量MAX在此处不可得 ``` +const命令也不存在提升,只能在声明的位置后面使用。 + +```javascript + +if (true) { + console.log(MAX); // ReferenceError + const MAX = 5; +} + +``` + +上面代码在常量MAX声明之前就调用,结果报错。 + const声明的常量,也与let一样不可重复声明。 ```javascript @@ -295,3 +325,23 @@ foo.prop = 123; // 不起作用 ``` 上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用。 + +## 全局对象的属性 + +全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。ES5规定,所有全局变量都是全局对象的属性。 + +ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。 + +```javascript + +var a = 1; +// 如果在node环境,可以写成global.a +// 或者采用通用方法,写成this.a +window.a // 1 + +let b = 1; +window.b // undefined + +``` + +上面代码中,全家变量a由var命令声明,所以它是全局对象的属性;全局变量b由let命令声明,所以它不是全局对象的属性,返回undefined。 From 262518bc91547db7093341b71d76167a614db47e Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Wed, 18 Feb 2015 10:08:52 +0800 Subject: [PATCH 0044/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9let=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=88=A0=E9=99=A4=E7=9A=84=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/intro.md | 6 ++++-- docs/let.md | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/intro.md b/docs/intro.md index 9be78ec9a..35e12c171 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -247,8 +247,10 @@ ES7可能包括的功能有: (1)**Object.observe**:用来监听对象(以及数组)的变化。一旦监听对象发生变化,就会触发回调函数。 -(2)**Multi-Threading**:多线程支持。目前,Intel和Mozilla有一个共同的研究项目RiverTrail,致力于让JavaScript多线程运行。预计这个项目的研究成果会被纳入ECMAScript标准。 +(2)**Async函数**:在Promise和Generator函数基础上,提出的异步操作解决方案。 -(3)**Traits**:它将是“类”功能(class)的一个替代。通过它,不同的对象可以分享同样的特性。 +(3)**Multi-Threading**:多线程支持。目前,Intel和Mozilla有一个共同的研究项目RiverTrail,致力于让JavaScript多线程运行。预计这个项目的研究成果会被纳入ECMAScript标准。 + +(4)**Traits**:它将是“类”功能(class)的一个替代。通过它,不同的对象可以分享同样的特性。 其他可能包括的功能还有:更精确的数值计算、改善的内存回收、增强的跨站点安全、类型化的更贴近硬件的低级别操作、国际化支持(Internationalization Support)、更多的数据结构等等。 diff --git a/docs/let.md b/docs/let.md index c866143a3..1cb81176e 100644 --- a/docs/let.md +++ b/docs/let.md @@ -129,7 +129,11 @@ if (true) { ```javascript -// 报错 +function bar(x=y, y=2) { + return [x, y]; +} + +bar(); // 报错 ``` From 0c9997606470cf85a5093454f1e5af78d3c3908e Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Mon, 23 Feb 2015 16:52:08 +0800 Subject: [PATCH 0045/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9function=20&=20clas?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 14 ++++++++++++++ docs/function.md | 30 ++++++++++++++++++++++++++---- docs/reference.md | 3 ++- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/docs/class.md b/docs/class.md index fb91cb0d1..239ece035 100644 --- a/docs/class.md +++ b/docs/class.md @@ -694,6 +694,20 @@ customName(); // 'foo' 上面代码的import命令,可以用任意名称指向输出的匿名函数。需要注意的是,这时import命令后面,不使用大括号。 +```javascript + +// 第一组 +import crc32 from 'crc32'; +export default function crc32(){} + +// 第二组 +import { crc32 } from 'crc32'; +export function crc32(){}; + +``` + +上面代码的两组写法,第一组是使用export default时,对应的import语句不需要使用大括号;第二组是不使用export default时,对应的import语句需要使用大括号。 + export default命令用在非匿名函数前,也是可以的。 ```javascript diff --git a/docs/function.md b/docs/function.md index ecf8f7db7..1632c0fd9 100644 --- a/docs/function.md +++ b/docs/function.md @@ -28,13 +28,11 @@ if (typeof y === 'undefined') { y = 'World'; } - // 写法二 if (arguments.length === 1) { y = 'World'; } - ``` ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。 @@ -65,6 +63,30 @@ var p = new Point(); ``` +默认值的写法非常灵活,下面是一个为对象属性设置默认值的例子。 + +```javascript + +fetch(url, { body='', method='GET', headers={} }){ + console.log(method); +} + +``` + +上面代码中,传入函数fetch的第二个参数是一个对象,调用的时候可以为它的三个属性设置默认值。这个例子也说明,不仅函数定义时,可以设置参数默认值,而且函数调用时,也可以设置参数默认值。 + +甚至还可以设置双重默认值。 + +```javascript + +fetch(url, { method='GET' } = {}){ + console.log(method); +} + +``` + +上面代码中,调用函数fetch时,如果不含第二个参数,则默认值为一个空对象;如果包含第二个参数,则它的method属性默认值为GET。 + 定义了默认值的参数,必须是函数的尾部参数,其后不能再有其他无默认值的参数。这是因为有了默认值以后,该参数可以省略,只有位于尾部,才可能判断出到底省略了哪些参数。 ```javascript @@ -111,11 +133,11 @@ foo(undefined, null) ```javascript function throwIfMissing() { - throw new Error('Missing parameter'); + throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { - return mustBeProvided; + return mustBeProvided; } foo() diff --git a/docs/reference.md b/docs/reference.md index 165cbf7e6..ac069eedb 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -20,7 +20,7 @@ - Traceur-compiler, [Language Features](https://github.com/google/traceur-compiler/wiki/LanguageFeatures): Traceur文档列出的一些ES6例子 - Axel Rauschmayer, [ECMAScript 6: what’s next for JavaScript?](https://speakerdeck.com/rauschma/ecmascript-6-whats-next-for-javascript-august-2014): 关于ES6新增语法的综合介绍,有很多例子 - Toby Ho, [ES6 in io.js](http://davidwalsh.name/es6-io) - +- Guillermo Rauch, [ECMAScript 6](http://rauchg.com/2015/ecmascript-6/) ## 语法点 @@ -104,3 +104,4 @@ - esnext, [ES6 Module Transpiler](https://github.com/esnext/es6-module-transpiler):基于node.js的将ES6模块转为ES5代码的命令行工具 - Sebastian McKenzie, [6to5](https://github.com/sebmck/6to5): 将ES6转为ES5代码的Node模块,支持source map - SystemJS, [SystemJS](https://github.com/systemjs/systemjs): 在浏览器中加载AMD、CJS、ES6模块的一个垫片库 +- Facebook, [regenerator](https://github.com/facebook/regenerator): 将Generator函数转为ES5的转码器 From caa4f9e99372d3b87e34dacc99638ed628ae1a0e Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Thu, 26 Feb 2015 01:09:05 +0800 Subject: [PATCH 0046/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/array.md | 44 +++++++++++++++++++++++++++++++++++++ docs/function.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++- docs/object.md | 18 ++++++++++++++- docs/reference.md | 2 ++ 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/docs/array.md b/docs/array.md index fcb92a2c0..770bf1dcc 100644 --- a/docs/array.md +++ b/docs/array.md @@ -37,6 +37,16 @@ Array.from({ 0: "a", 1: "b", 2: "c", length: 3 }); ``` +对于还没有部署该方法的浏览器,可以用Array.prototyp.slice方法替代。 + +```javascript + +const toArray = (() => + Array.from ? Array.from : obj => [].slice.call(obj) +)(); + +``` + Array.from()还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理。 ```JavaScript @@ -170,6 +180,40 @@ for (let [index, elem] of ['a', 'b'].entries()) { ``` +## 数组实例的includes() + +Array.protypeto.includes方法返回一个布尔值,表示某个数组是否包含给定的值。该方法属于ES7。 + +```javascript + +[1, 2, 3].includes(2); // true +[1, 2, 3].includes(4); // false +[1, 2, NaN].includes(NaN); // true + +``` + +该方法的第二个参数表示搜索的起始位置,默认为0。 + +```javascript + +[1, 2, 3].includes(3, 3); // false +[1, 2, 3].includes(3, -1); // true + +``` + +下面代码用来检查当前环境是否支持该方法,如果不支持,部署一个简易的替代版本。 + +```javascript + +const contains = (() => + Array.prototype.includes + ? (arr, value) => arr.includes(value) + : (arr, value) => arr.some(el => el === value) +)(); +contains(["foo", "bar"], "baz"); // => false + +``` + ## 数组推导 ES6提供简洁写法,允许直接通过现有数组生成新数组,这被称为数组推导(array comprehension)。 diff --git a/docs/function.md b/docs/function.md index 1632c0fd9..4e97044d6 100644 --- a/docs/function.md +++ b/docs/function.md @@ -203,7 +203,22 @@ add(2, 5, 3) // 10 上面代码的add函数是一个求和函数,利用rest参数,可以向该函数传入任意数目的参数。 -前面说过,rest参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用rest参数改写数组push方法的例子。 +下面是一个rest参数代替arguments变量的例子。 + +```javascript + +// arguments变量的写法 +const sortNumbers = () => + Array.prototype.slice.call(arguments).sort(); + +// rest参数的写法 +const sortNumbers = (...numbers) => numbers.sort(); + +``` + +上面代码的两种写法,比较后可以发现,rest参数的写法更自然也更简洁。 + +rest参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用rest参数改写数组push方法的例子。 ```javascript @@ -510,6 +525,17 @@ var getTempItem = id => ({ id: id, name: "Temp" }); ``` +箭头函数使得表达更加简洁。 + +```javascript + +const isEven = n => n % 2 == 0; +const square = n => n * n; + +``` + +上面代码只用了两行,就定义了两个简单的工具函数。如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。 + 箭头函数的一个用处是简化回调函数。 ```javascript @@ -585,3 +611,31 @@ var handler = { 由于this在箭头函数中被绑定,所以不能用call()、apply()、bind()这些方法去改变this的指向。 长期以来,JavaScript语言的this对象一直是一个令人头痛的问题,在对象方法中使用this,必须非常小心。箭头函数绑定this,很大程度上解决了这个困扰。 + +箭头函数内部,还可以再使用箭头函数。下面是一个部署管道机制(pipeline)的例子。 + +```javascript + +const pipeline = (...funcs) => + val => funcs.reduce((a, b) => b(a), val); + +const plus1 = a => a + 1; +const mult2 = a => a * 2; +const addThenMult = pipeline(plus1, mult2); + +addThenMult(5) +// 12 + +``` + +上面的代码等同于下面的写法。 + +```javascript + +const plus1 = a => a + 1; +const mult2 = a => a * 2; + +mult2(plus1(5)) +// 12 + +``` diff --git a/docs/object.md b/docs/object.md index f267bb0ba..81e89a190 100644 --- a/docs/object.md +++ b/docs/object.md @@ -271,7 +271,23 @@ function clone(origin) { ``` -**(4)为属性指定默认值** +**(4)合并多个对象** + +将多个对象合并到某个对象。 + +```javascript +const merge = + (target, ...sources) => Object.assign(target, ...sources); +``` + +如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。 + +```javascript +const merge = + (...sources) => Object.assign({}, ...sources); +``` + +**(5)为属性指定默认值** ```javascript diff --git a/docs/reference.md b/docs/reference.md index ac069eedb..b0c364c33 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -21,6 +21,8 @@ - Axel Rauschmayer, [ECMAScript 6: what’s next for JavaScript?](https://speakerdeck.com/rauschma/ecmascript-6-whats-next-for-javascript-august-2014): 关于ES6新增语法的综合介绍,有很多例子 - Toby Ho, [ES6 in io.js](http://davidwalsh.name/es6-io) - Guillermo Rauch, [ECMAScript 6](http://rauchg.com/2015/ecmascript-6/) +- Charles King, [The power of ECMAScript 6](http://charlesbking.com/power_of_es6/#/) +- Benjamin De Cock, [Frontend Guidelines](https://github.com/bendc/frontend-guidelines): ES6最佳实践 ## 语法点 From d37c6ff2a4ed1bb89c539ce9e2a92ca05129e7ee Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 27 Feb 2015 10:30:48 +0800 Subject: [PATCH 0047/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9iterator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/iterator.md | 185 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 137 insertions(+), 48 deletions(-) diff --git a/docs/iterator.md b/docs/iterator.md index db07920bf..6f2ef86bf 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -2,11 +2,15 @@ ## Iterator(遍历器) -### 语法 +### 概念 -遍历器(Iterator)是一种接口规格,任何对象只要部署这个接口,就可以完成遍历操作。它的作用有两个,一是为各种数据结构,提供一个统一的、简便的接口,二是使得对象的属性能够按某种次序排列。在ES6中,遍历操作特指for...of循环,即Iterator接口主要供for...of循环使用。 +JavaScript原有的数据结构,主要是数组(Array)和对象(Object),ES6又添加了Map和Set,用户还可以组合使用它们,定义自己的数据结构。这就需要一种统一的接口机制,来处理所有不同的数据结果。 -遍历器提供了一个指针,指向当前对象的某个属性,使用next方法,就可以将指针移动到下一个属性。next方法返回一个包含value和done两个属性的对象。其中,value属性是当前遍历位置的值,done属性是一个布尔值,表示遍历是否结束。下面是一个模拟next方法返回值的例子。 +遍历器(Iterator)就是这样一种机制。它属于一种接口规格,任何数据结构只要部署这个接口,就可以完成遍历操作,即依次处理该结构的所有成员。它的作用有两个,一是为各种数据结构,提供一个统一的、简便的接口,二是使得数据结构的成员能够按某种次序排列。在ES6中,遍历操作特指for...of循环,即Iterator接口主要供for...of消费。 + +遍历器的遍历过程是这样的:它提供了一个指针,默认指向当前数据结构的起始位置。第一次调用遍历器的next方法,可以将指针指向到第一个成员,第二次调用就指向第二个成员,直至指向数据结构的结束位置。每一次调用,都会返回当前成员的信息,具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。 + +下面是一个模拟next方法返回值的例子。 ```javascript @@ -28,9 +32,10 @@ it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true } ``` -上面代码定义了一个makeIterator函数,它的作用是返回一个遍历器对象,用来遍历参数数组。next方法依次遍历数组的每个成员,请特别注意,next返回值的构造。 -下面是一个无限运行的遍历器例子。 +上面代码定义了一个makeIterator函数,它的参数是一个数组。调用该函数,就会返回一个对象。这个对象具有一个next方法,每次调用next方法,它的内部指针就会指向数组的下一个成员,并返回一个该成员信息的对象。请特别注意,next方法的返回值的构造:一个具有value和done两个属性的对象。通过这个返回值,我们就可以知道当前成员的值是什么,以及遍历是否结束。在这个例子中,makeIterator函数用来生成遍历器,它返回的那个具有next方法的对象就是遍历器,调用遍历器的next方法,就可以遍历事先给定的数组。 + +因为遍历器的作用,只是把接口规格加到数据结构之上。所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器,或者说用遍历器模拟出数据结构。下面是一个无限运行的遍历器例子。 ```javascript @@ -53,15 +58,37 @@ it.next().value // '2' ``` -上面的例子,说明了next方法返回值的结构:value和done两个属性。 +上面的例子中,idMaker函数返回的对象就是遍历器,但是并没有对应的数据结构,或者说遍历器自己描述了一个数据结构出来。 -### Iterator接口的部署 +总之,所谓Iterator接口,就是指调用这个接口,会返回一个遍历器对象。该对象具备next方法,每次调用该方法,会返回一个具有value和done两个属性的新对象,指向部署了Iterator接口的数据结构的一个成员。 -具有Iterator接口的对象,都能被for...of循环遍历(见后文的介绍)。所谓Iterator接口,就是指它会返回一个遍历器对象,该对象具备next方法,每次调用该方法,会依次返回一个具有上节提到的value和done两个属性的新对象,指向原对象的一个成员。 +### 默认的Iterator接口 -在ES6中,有三类数据结构原生具备Iterator接口:数组、类似数组的对象、Set和Map结构。除此之外,其他数据结构(主要是对象)的Iterator接口都需要自己部署。其他对象需要手动部署Iterator接口,让其返回一个遍历器。 +Iterator接口的开发目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(见后文的介绍)。当使用for...of循环,遍历某种数据结构时,该循环会自动去寻找Iterator接口。 -一个对象如果要有Iterator接口,必须部署一个@@iterator方法(原型链上的对象具有该方法也可),该方法部署在一个键名为`Symbol.iterator`的属性上,对应的键值是一个函数,该函数返回一个遍历器对象。 +ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性。也就是说,调用Symbol.iterator方法,就会得到当前数据结构的默认遍历器。Symbol.iterator是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一节)。 + +在ES6中,有三类数据结构原生具备Iterator接口:数组、类似数组的对象、Set和Map结构。 + +```javascript + +let arr = ['a', 'b', 'c']; +let iter = arr[Symbol.iterator](); + +iter.next() // { value: 'a', done: false } +iter.next() // { value: 'b', done: false } +iter.next() // { value: 'c', done: false } +iter.next() // { value: undefined, done: true } + +``` + +上面代码中,变量arr是一个数组,原生就具有遍历器接口,部署在arr的Symbol.iterator属性上面。所以,调用这个属性,就得到遍历器。 + +上面提到,原生就部署iterator接口的数据结构有三类,对于这三类数据结构,不用自己写遍历器,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的Iterator接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。 + +对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作Map结构使用,ES5没有Map结构,而ES6原生提供了。 + +一个对象如果要有可被for...of循环调用的Iterator接口,就必须在Symbol.iterator的属性上部署遍历器方法(原型链上的对象具有该方法也可)。 ```javascript @@ -75,18 +102,16 @@ class MySpecialTree { ``` -上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一节)。这里要注意,@@iterator的键名是`Symbol.iterator`,键值是一个方法(函数),该方法执行后,返回一个当前对象的遍历器。 - -下面是一个例子。 +上面代码是一个类部署Iterator接口的写法。Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器。下面是一个例子。 ```javascript -function O(value){ +function Obj(value){ this.value = value; this.next = null; } -O.prototype[Symbol.iterator] = function(){ +Obj.prototype[Symbol.iterator] = function(){ var iterator = { next: next @@ -112,9 +137,9 @@ O.prototype[Symbol.iterator] = function(){ return iterator; } -var one = new O(1); -var two = new O(2); -var three = new O(3); +var one = new Obj(1); +var two = new Obj(2); +var three = new Obj(3); one.next = two; two.next = three; @@ -155,7 +180,7 @@ let obj = { ``` -对于类似数组的对象,部署Iterator接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数值的Iterator接口。 +对于类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数值的Iterator接口。 ```javascript @@ -163,7 +188,7 @@ NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; ``` -如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。 +如果Symbol.iterator方法返回的不是遍历器,解释引擎将会报错。 ```javascript @@ -175,7 +200,7 @@ obj[Symbol.iterator] = () => 1; ``` -上面代码中,变量obj的@@iterator方法返回的不是遍历器,因此报错。 +上面代码中,变量obj的Symbol.iterator方法返回的不是遍历器,因此报错。 ### 原生具备iterator接口的数据结构 @@ -277,7 +302,11 @@ for (let x of obj) { ## for...of循环 -ES6中,一个对象只要部署了@@iterator方法,就被视为具有iterator接口,就可以用for...of循环遍历它的值。也就是说,for...of循环内部调用是原对象的`Symbol.iterator`方法。 +ES6中,一个数据结构只要部署了Symbol.iterator方法,就被视为具有iterator接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。 + +for...of循环可以使用的范围包括数组、Set和Map结构、类似数组的对象(比如arguments对象、DOM NodeList对象)、后文的Generator对象,以及字符串。 + +### 数组 数组原生具备iterator接口。 @@ -298,8 +327,8 @@ for(let v of arr) { const arr = ['red', 'green', 'blue']; arr.forEach(function (element, index) { - console.log(element); // red green blue - console.log(index); // 0 1 2 + console.log(element); // red green blue + console.log(index); // 0 1 2 }); ``` @@ -309,6 +338,7 @@ JavaScript原有的for...in循环,只能获得对象的键名,不能直接 ```javascript var arr = ["a", "b", "c", "d"]; + for (a in arr) { console.log(a); // 0 1 2 3 } @@ -321,13 +351,15 @@ for (a of arr) { 上面代码表明,for...in循环读取键名,for...of循环读取键值。如果要通过for...of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法,参见《数组的扩展》章节。 +### Set和Map结构 + Set和Map结构也原生具有Iterator接口,可以直接使用for...of循环。 ```javascript var engines = Set(["Gecko", "Trident", "Webkit", "Webkit"]); for (var e of engines) { - console.log(e); + console.log(e); } // Gecko // Trident @@ -346,52 +378,109 @@ for (var [name, value] of es6) { ``` -上面代码演示了如何遍历Set结构和Map结构,后者是同时遍历键名和键值。 - -对于普通的对象,for...of结构不能直接使用,会报错,必须部署了iterator接口后才能使用。但是,这样情况下,for...in循环依然可以用来遍历键名。 +上面代码演示了如何遍历Set结构和Map结构。值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set结构遍历时,返回的是一个值,而Map结构遍历时,返回的是一个数组,该数组的两个成员分别为当前Map成员的键名和键值。 ```javascript -var es6 = { - edition: 6, - committee: "TC39", - standard: "ECMA-262" -}; - -for (e in es6) { - console.log(e); +let map = new Map().set('a', 1).set('b', 2); +for (let pair of map) { + console.log(pair); } -// edition -// committee -// standard +// ['a', 1] +// ['b', 2] -for (e of es6) { - console.log(e); +``` + +### 计算生成的数据结构 + +ES6的数组、Set、Map都部署了以下三个方法,调用后都返回遍历器。 + +- entries() 返回一个遍历器,用来遍历 [键名, 键值] 组成的数组。对于数组,键名就是索引值;对于Set,键名与键值相同。Map结构的iterator接口,默认就是调用entries方法。 +- keys() 返回一个遍历器,用来遍历所有的键名。 +- values() 返回一个遍历器,用来遍历所有的键值。 + +这三个方法调用后生成的遍历器,所遍历的都是计算生成的数据结构。 + +```javascript + +let arr = ['a', 'b', 'c']; +for (let pair of arr.entries()) { + console.log(pair); } -// TypeError: es6 is not iterable +// [0, 'a'] +// [1, 'b'] +// [2, 'c'] ``` -上面代码表示,for...in循环可以遍历键名,for...of循环会报错。 +### 类似数组的对象 -总结一下,for...of循环可以使用的范围包括数组、类似数组的对象(比如arguments对象、DOM NodeList对象)、Set和Map结构、后文的Generator对象,以及字符串。下面是for...of循环用于字符串和DOM NodeList对象的例子。 +类似数组的对象包括好几类。下面是for...of循环用于字符串、DOM NodeList对象、arguments对象的例子。 ```javascript -// 字符串的例子 - +// 字符串 let str = "hello"; for (let s of str) { console.log(s); // h e l l o } -// DOM NodeList对象的例子 - +// DOM NodeList对象 let paras = document.querySelectorAll("p"); for (let p of paras) { p.classList.add("test"); } +// arguments对象 +function printArgs() { + for (let x of arguments) { + console.log(x); + } +} +printArgs('a', 'b'); +// 'a' +// 'b' + ``` + +对于字符串来说,for...of循环还有一个特点,就是会正确识别32位UTF-16字符。 + +```javascript + +for (let x of 'a\uD83D\uDC0A') { + console.log(x); +} +// 'a' +// '\uD83D\uDC0A' + +``` + +### 对象 + +对于普通的对象,for...of结构不能直接使用,会报错,必须部署了iterator接口后才能使用。但是,这样情况下,for...in循环依然可以用来遍历键名。 + +```javascript + +var es6 = { + edition: 6, + committee: "TC39", + standard: "ECMA-262" +}; + +for (e in es6) { + console.log(e); +} +// edition +// committee +// standard + +for (e of es6) { + console.log(e); +} +// TypeError: es6 is not iterable + +``` + +上面代码表示,对于普通的对象,for...in循环可以遍历键名,for...of循环会报错。 From f1c2415227aa0c8541a73d74666e7621e74ee191 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 27 Feb 2015 14:59:18 +0800 Subject: [PATCH 0048/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9iterator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/iterator.md | 112 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 5 deletions(-) diff --git a/docs/iterator.md b/docs/iterator.md index 6f2ef86bf..ff8ccd100 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -62,13 +62,32 @@ it.next().value // '2' 总之,所谓Iterator接口,就是指调用这个接口,会返回一个遍历器对象。该对象具备next方法,每次调用该方法,会返回一个具有value和done两个属性的新对象,指向部署了Iterator接口的数据结构的一个成员。 +如果使用TypeScript的写法,遍历器接口、遍历器和遍历器返回值的规格可以描述如下。 + +```javascript + +interface Iterable { + [System.iterator]() : Iterator, +} + +interface Iterator { + next(value?: any) : IterationResult, +} + +interface IterationResult { + value: any, + done: boolean, +} + +``` + ### 默认的Iterator接口 Iterator接口的开发目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(见后文的介绍)。当使用for...of循环,遍历某种数据结构时,该循环会自动去寻找Iterator接口。 ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性。也就是说,调用Symbol.iterator方法,就会得到当前数据结构的默认遍历器。Symbol.iterator是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一节)。 -在ES6中,有三类数据结构原生具备Iterator接口:数组、类似数组的对象、Set和Map结构。 +在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。 ```javascript @@ -202,6 +221,62 @@ obj[Symbol.iterator] = () => 1; 上面代码中,变量obj的Symbol.iterator方法返回的不是遍历器,因此报错。 +### 调用默认iterator接口的场合 + +有一些场合会默认调用iterator接口(即Symbol.iterator方法),除了下文会介绍的for...of循环,还有几个别的场合。 + +**(1)解构赋值** + +对数组和Set结构进行解构赋值时,会默认调用iterator接口。 + +```javascript + +let set = new Set().add('a').add('b').add('c'); + +let [x,y] = set; +// x='a'; y='b' + +let [first, ...rest] = set; +// first='a'; rest=['b','c']; + +``` + +**(2)扩展运算符** + +扩展运算符(...)也会调用默认的iterator接口。 + +```javascript + +// 例一 +var str = 'hello'; +[...str] // ['h','e','l','l','o'] + +// 例二 +let arr = ['b', 'c']; +['a', ...arr, 'd'] +// ['a', 'b', 'c', 'd'] + +``` + +上面代码的扩展运算符内部就调用iterator接口。 + +实际上,这提供了一种简便机制,可以将任何部署了iterator接口的对象,转为对象。 + +```javascript + +let arr = [...iterable]; + +``` + +**(3)其他场合** + +以下场合也会用到默认的iterator接口,可以查阅相关章节。 + +- yield* +- Array.from() +- Map(), Set(), WeakMap(), WeakSet() +- Promise.all(), Promise.race() + ### 原生具备iterator接口的数据结构 《数组的扩展》一章中提到,ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器。 @@ -221,7 +296,7 @@ arrEntries === arrEntries[Symbol.iterator]() 上面代码中,entries方法返回的是一个遍历器(iterator),本质上就是调用了`Symbol.iterator`方法。 -字符串是一个类似数组的对象,因此也原生具有Iterator接口。 +字符串是一个类似数组的对象,也原生具有Iterator接口。 ```javascript @@ -304,23 +379,30 @@ for (let x of obj) { ES6中,一个数据结构只要部署了Symbol.iterator方法,就被视为具有iterator接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。 -for...of循环可以使用的范围包括数组、Set和Map结构、类似数组的对象(比如arguments对象、DOM NodeList对象)、后文的Generator对象,以及字符串。 +for...of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象(比如arguments对象、DOM NodeList对象)、后文的Generator对象,以及字符串。 ### 数组 -数组原生具备iterator接口。 +数组原生具备iterator接口,for...of循环本质上就是调用这个接口产生的遍历器,可以用下面的代码证明。 ```javascript const arr = ['red', 'green', 'blue']; +let iterator = arr[Symbol.iterator](); for(let v of arr) { console.log(v); // red green blue } +for(let v of iterator) { + console.log(v); // red green blue +} + ``` -上面代码说明,for...of循环可以代替数组实例的forEach方法。 +上面代码的for...of循环的两种写法是等价的。 + +for...of循环可以代替数组实例的forEach方法。 ```javascript @@ -457,6 +539,24 @@ for (let x of 'a\uD83D\uDC0A') { ``` +并不是所有类似数组的对象都具有iterator接口,一个简便的解决方法,就是使用Array.from方法将其转为数组。 + +```javascript + +let arrayLike = { length: 2, 0: 'a', 1: 'b' }; + +// 报错 +for (let x of arrayLike) { + console.log(x); +} + +// 正确 +for (let x of Array.from(arrayLike)) { + console.log(x); +} + +``` + ### 对象 对于普通的对象,for...of结构不能直接使用,会报错,必须部署了iterator接口后才能使用。但是,这样情况下,for...in循环依然可以用来遍历键名。 @@ -484,3 +584,5 @@ for (e of es6) { ``` 上面代码表示,对于普通的对象,for...in循环可以遍历键名,for...of循环会报错。 + +在对象上部署iterator接口的代码,参见本章前面部分。 From 890861be03a5bbfddc4fea48b952113002b4fb02 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 1 Mar 2015 13:59:58 +0800 Subject: [PATCH 0049/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9promise/async?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/promise.md | 10 ++++++++-- docs/reference.md | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index a0779c482..0e3ca40e2 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -439,10 +439,16 @@ async函数并不属于ES6,而是被列入了ES7,但是traceur编译器和re ```javascript async function getStockPrice(symbol, currency) { - let price = await getStockPrice(symbol); - return convert(price, currency); + let price = await getStockPrice(symbol); + return convert(price, currency); } +getStockPrice("JNJ") + .then( + price => console.log(price), + error => console.error(error) + ); + ``` 上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数将返回一个Promise对象。调用该函数时,当遇到await关键字,立即返回它后面的表达式(getStockPrice函数)产生的Promise对象,不再执行函数体内后面的语句。等到getStockPrice完成,再自动回到函数体内,执行剩下的语句。 diff --git a/docs/reference.md b/docs/reference.md index b0c364c33..78de7afc4 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -67,6 +67,7 @@ - jmar777, [What's the Big Deal with Generators?](http://devsmash.com/blog/whats-the-big-deal-with-generators) - Marc Harter, [Generators in Node.js: Common Misconceptions and Three Good Use Cases](http://strongloop.com/strongblog/how-to-generators-node-js-yield-use-cases/): 讨论Generator函数的作用 - Axel Rauschmayer, [Iterators and generators in ECMAScript 6](http://www.2ality.com/2013/06/iterators-generators.html): 探讨Iterator和Generator的设计目的 +- Axel Rauschmayer, [Iterables and iterators in ECMAScript 6](http://www.2ality.com/2015/02/es6-iteration.html): Iterator的详细介绍 - StackOverflow, [ES6 yield : what happens to the arguments of the first call next()?](http://stackoverflow.com/questions/20977379/es6-yield-what-happens-to-the-arguments-of-the-first-call-next): 第一次使用next方法时不能带有参数 - Kyle Simpson, [ES6 Generators: Complete Series](http://davidwalsh.name/es6-generators): 由浅入深探讨Generator的系列文章,共四篇 - Gajus Kuizinas, [The Definitive Guide to the JavaScript Generators](http://gajus.com/blog/2/the-definetive-guide-to-the-javascript-generators): 对Generator的综合介绍 @@ -84,6 +85,7 @@ - Axel Rauschmayer, [ECMAScript 6 promises (2/2): the API](http://www.2ality.com/2014/10/es6-promises-api.html): 对ES6 Promise规格和用法的详细介绍 - Jack Franklin, [Embracing Promises in JavaScript](http://javascriptplayground.com/blog/2015/02/promises/): catch方法的例子 - Luke Hoban, [Async Functions for ECMAScript](https://github.com/lukehoban/ecmascript-asyncawait): Async函数的设计思想,与Promise、Gernerator函数的关系 +- Jafar Husain, [Asynchronous Generators for ES7](https://github.com/jhusain/asyncgenerator): Async函数的深入讨论 ## Class与模块 From 5618f6e5e7bdbb8a84bf84741c18aded1004bb46 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 3 Mar 2015 23:30:21 +0800 Subject: [PATCH 0050/1372] =?UTF-8?q?=E6=96=B0=E5=BB=BAstyle.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/function.md | 6 +- docs/reference.md | 1 + docs/string.md | 4 + docs/style.md | 241 ++++++++++++++++++++++++++++++++++++++++++++++ sidebar.md | 1 + 5 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 docs/style.md diff --git a/docs/function.md b/docs/function.md index 4e97044d6..12f523e97 100644 --- a/docs/function.md +++ b/docs/function.md @@ -54,8 +54,8 @@ log('Hello', '') // Hello ```javascript function Point(x = 0, y = 0) { - this.x = x; - this.y = y; + this.x = x; + this.y = y; } var p = new Point(); @@ -63,6 +63,8 @@ var p = new Point(); ``` +除了简洁,ES6的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本彻底拿到这个参数,也不会导致以前的代码无法运行。 + 默认值的写法非常灵活,下面是一个为对象属性设置默认值的例子。 ```javascript diff --git a/docs/reference.md b/docs/reference.md index 78de7afc4..37d393570 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -23,6 +23,7 @@ - Guillermo Rauch, [ECMAScript 6](http://rauchg.com/2015/ecmascript-6/) - Charles King, [The power of ECMAScript 6](http://charlesbking.com/power_of_es6/#/) - Benjamin De Cock, [Frontend Guidelines](https://github.com/bendc/frontend-guidelines): ES6最佳实践 +- Jani Hartikainen, [ES6: What are the benefits of the new features in practice?](http://codeutopia.net/blog/2015/01/06/es6-what-are-the-benefits-of-the-new-features-in-practice/) ## 语法点 diff --git a/docs/string.md b/docs/string.md index 4d878b7d7..740109deb 100644 --- a/docs/string.md +++ b/docs/string.md @@ -535,11 +535,15 @@ tag`First line\nSecond line` 上面代码中,tag函数的第一个参数是一个数组`["First line\nSecond line"]`,这个数组有一个raw属性,等于`["First line\\nSecond line"]`,两者唯一的区别就是斜杠被转义了。 +```javascript + function tag(strings) { console.log(strings.raw[0]); // "First line\\nSecond line" } +``` + ## String.raw() String.raw方法,往往用来充当模板字符串的处理函数,返回字符串被转义前的原始格式。 diff --git a/docs/style.md b/docs/style.md new file mode 100644 index 000000000..fc631dded --- /dev/null +++ b/docs/style.md @@ -0,0 +1,241 @@ +# 编程风格 + +本章探讨如何将ES6的新语法,运用到编码实践之中,与传统的JavaScript语法结合在一起,以及如何形成良好的编码风格。 + +## 块级作用域 + +**(1)let取代var** + +ES6提出了两个新的声明变量的命令:let和const。其中,let完全可以取代var,因为两者语义相同,而且let没有副作用。 + +```javascript + +"use strict"; + +if(true) { + let x = 'hello'; +} + +for (let i = 0; i < 10; i++) { + console.log(i); +} + +``` + +上面代码如果用var替代let,实际上就声明了一个全局变量,这显然不是本意。变量应该只在其声明的代码块内有效,var命令做不到这一点。 + +var命令存在变量提升效用,let命令没有这个问题。 + +```javascript + +"use strict"; + +if(true) { + console.log(x); // ReferenceError + let x = 'hello'; +} + +``` + +上面代码如果使用var替代let,console.log那一行就不会报错,而是会输出undefined,因为变量声明提升到代码块的头部。这违反了变量先声明后使用的原则。 + +所以,建议不再使用var命令,而是使用let命令取代。 + +**(2)全局常量和线程安全** + +在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。这符合函数式编程思想,有利于将来的分布式运算。 + +```javascript + +// bad +var a = 1, b = 2, c = 3; + +// good +const a = 1; +const b = 2; +const c = 3; + +// best +const [a, b, c] = [1, 2, 3]; + +``` + +const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。 + +所有的函数都应该设置为常量。 + +let表示的变量,只应出现在单线程运行的代码中,不能是多线程共享的,这样有利于保证线程安全。 + +**(3)严格模式** + +V8引擎只在严格模式之下,支持let和const。结合前两点,这实际上意味着,将来所有的编程都是针对严格模式的。 + +## 字符串 + +静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。 + +```javascript + +// bad +const a = "foobar"; +const b = 'foo' + a + 'bar'; + +// acceptable +const c = `foobar`; + +// good +const a = 'foobar'; +const b = `foo${a}bar`; +const c = 'foobar'; + +``` + +## 对象 + +单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。 + +```javascript + +// bad +const a = { k1: v1, k2: v2, }; +const b = { + k1: v1, + k2: v2 +}; + +// good +const a = { k1: v1, k2: v2 }; +const b = { + k1: v1, + k2: v2, +}; + +``` + +对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。 + +```javascript + +// bad +const a = {}; +a.x = 3; + +// if reshape unavoidable +const a = {}; +Object.assign(a, { x: 3 }); + +// good +const a = { x: null }; +a.x = 3; + +``` + +## 函数 + +使用匿名函数的场合,一律改为使用箭头函数。 + +```javascript + +// bad +arr.reduce(function(x, y) { return x + y; }, 0); + +// good +arr.reduce((x, y) => x + y, 0); + +``` + +箭头函数取代Function.prototype.bind,不应再用 self / _this / that 绑定 this。 + +```javascript + +// bad +const self = this; +const boundMethod = function(...params) { + return method.apply(self, params); +} + +// acceptable +const boundMethod = method.bind(this); + +// best +const boundMethod = (...params) => method.apply(this, params); + +``` + +所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。 + +```bash + +// bad +function divide(a, b, option = false ) { +} + +// good +function divide(a, b, { option = false } = {}) { +} + +``` + +## Map结构 + +注意区分Object和Map,只有模拟实体对象时,才使用Object。如果只是需要key:value的数据结构,使用Map。因为Map有内建的遍历机制。 + +```javascript + +let map = new Map(arr); + +for (let key of map.keys()) { + console.log(key); +} + +for (let value of map.values()) { + console.log(value); +} + +for (let item of map.entries()) { + console.log(item[0], item[1]); +} + +``` + +## 模块 + +使用import取代require。 + +```javascript +// bad +const moduleA = require('moduleA'); +const func1 = moduleA.func1; +const func2 = moduleA.func2; + +// good +import { func1, func2 } from 'moduleA'; +``` + +使用export取代module.exports。 + +```javascript + +// commonJS的写法 +var React = require('react'); + +var Breadcrumbs = React.createClass({ + render() { + return <nav />; + } +}); + +module.exports = Breadcrumbs; + +// ES6的写法 +import React from 'react'; + +const Breadcrumbs = React.createClass({ + render() { + return <nav />; + } +}); + +export default Breadcrumbs + +``` diff --git a/sidebar.md b/sidebar.md index b031a01c6..ce8d4baca 100644 --- a/sidebar.md +++ b/sidebar.md @@ -19,6 +19,7 @@ 1. [Generator函数](#docs/generator) 1. [Promise对象](#docs/promise) 1. [Class和Module](#docs/class) +1. [编程风格](#docs/style) 1. [参考链接](#docs/reference) ## 其他 From db97b3393192cd20a8e905107e1a4e15bba11394 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Wed, 4 Mar 2015 18:51:25 +0800 Subject: [PATCH 0051/1372] =?UTF-8?q?=E4=BF=AE=E6=94=B9let?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/function.md | 13 +++++++++++++ docs/let.md | 5 +++-- docs/style.md | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/function.md b/docs/function.md index 12f523e97..cde5fb315 100644 --- a/docs/function.md +++ b/docs/function.md @@ -167,6 +167,19 @@ foo(2) // 2 上面代码中,参数y的默认值等于x,由于处在函数作用域,所以x等于参数x,而不是全局变量x。 +参数变量是默认声明的,所以不能用let或const再次声明。 + +```javascript + +function foo(x = 5) { + let x = 1; // error + const x = 2; // error +} + +``` + +上面代码中,参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。 + 参数默认值可以与解构赋值,联合起来使用。 ```javascript diff --git a/docs/let.md b/docs/let.md index 1cb81176e..218c0b349 100644 --- a/docs/let.md +++ b/docs/let.md @@ -29,7 +29,6 @@ for(let i = 0; i < arr.length; i++){} console.log(i) //ReferenceError: i is not defined - ``` 上面代码的计数器i,只在for循环体内有效。 @@ -88,7 +87,7 @@ if (1) { ``` -上面代码中,由于块级作用域内typeof运行时,x还没有声明,所以会抛出一个ReferenceError。 +上面代码中,由于块级作用域内typeof运行时,x还没有值,所以会抛出一个ReferenceError。 只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。 @@ -105,6 +104,8 @@ if (true) { 上面代码中,存在全局量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。 +ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些命令,就会报错。 + 总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。 ```javascript diff --git a/docs/style.md b/docs/style.md index fc631dded..6f7aebaa9 100644 --- a/docs/style.md +++ b/docs/style.md @@ -221,7 +221,7 @@ var React = require('react'); var Breadcrumbs = React.createClass({ render() { - return <nav />; + return