From b4fb3a71b85de14913c3e3e11c3bf686045b4079 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Wed, 8 Jul 2015 16:37:02 +0800 Subject: [PATCH 0001/1267] edit Math --- docs/function.md | 6 +- docs/number.md | 293 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 245 insertions(+), 54 deletions(-) diff --git a/docs/function.md b/docs/function.md index 558ee600f..6674c8961 100644 --- a/docs/function.md +++ b/docs/function.md @@ -647,10 +647,10 @@ i// 等同于 bar.apply(foo, arguments); ``` -箭头函数还有一个功能,就是可以很方便地改写λ微积分。 +箭头函数还有一个功能,就是可以很方便地改写λ演算。 ```javascript -// λ微积分的写法 +// λ演算的写法 fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v))) // ES6的写法 @@ -658,7 +658,7 @@ var fix = f => (x => f(v => x(x)(v))) (x => f(v => x(x)(v))); ``` -上面两种写法,几乎是一一对应的。由于λ微积分对于计算机科学非常重要,这使得我们可以用ES6作为替代工具,探索计算机科学。 +上面两种写法,几乎是一一对应的。由于λ演算对于计算机科学非常重要,这使得我们可以用ES6作为替代工具,探索计算机科学。 ## 尾调用优化 diff --git a/docs/number.md b/docs/number.md index 828f23bd1..ea7ff8b28 100644 --- a/docs/number.md +++ b/docs/number.md @@ -5,13 +5,16 @@ ES6提供了二进制和八进制数值的新的写法,分别用前缀0b和0o表示。 ```javascript - 0b111110111 === 503 // true 0o767 === 503 // true - ``` -八进制用0o前缀表示的方法,将要取代已经在ES5中被逐步淘汰的加前缀0的写法。 +八进制不再允许使用前缀0表示,而改为使用前缀0o。 + +```javascript +011 === 9 // 不正确 +0o11 === 9 // 正确 +``` ## Number.isFinite(), Number.isNaN() @@ -20,7 +23,6 @@ ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方 Number.isFinite()用来检查一个数值是否非无穷(infinity)。 ```javascript - Number.isFinite(15); // true Number.isFinite(0.8); // true Number.isFinite(NaN); // false @@ -29,13 +31,11 @@ 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; @@ -48,7 +48,6 @@ ES5通过下面的代码,部署Number.isFinite方法。 writable: true }); })(this); - ``` Number.isNaN()用来检查一个值是否为NaN。 @@ -102,7 +101,6 @@ Number.isNaN("NaN") // false ES6将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。 ```javascript - // ES5的写法 parseInt("12.34") // 12 parseFloat('123.45#') // 123.45 @@ -110,7 +108,6 @@ parseFloat('123.45#') // 123.45 // ES6的写法 Number.parseInt("12.34") // 12 Number.parseFloat('123.45#') // 123.45 - ``` 这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。 @@ -132,7 +129,6 @@ Number.isInteger(true) // false ES5通过下面的代码,部署Number.isInteger()。 ```javascript - (function (global) { var floor = Math.floor, isFinite = global.isFinite; @@ -148,13 +144,11 @@ ES5通过下面的代码,部署Number.isInteger()。 writable: true }); })(this); - ``` JavaScript能够准确表示的整数范围在-2ˆ53 and 2ˆ53之间。ES6引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。 ```javascript - var inside = Number.MAX_SAFE_INTEGER; var outside = inside + 1; @@ -163,79 +157,276 @@ Number.isSafeInteger(inside) // true Number.isInteger(outside) // true Number.isSafeInteger(outside) // false - ``` ## Math对象的扩展 -**(1)Math.trunc()** +ES6在Math对象上新增了17个与数学相关的方法。所有这些方法都是静态方法,只能在Math对象上调用。 + +### Math.trunc() Math.trunc方法用于去除一个数的小数部分,返回整数部分。 ```javascript - Math.trunc(4.1) // 4 Math.trunc(4.9) // 4 Math.trunc(-4.1) // -4 Math.trunc(-4.9) // -4 - ``` -**(2)Math.sign()** +对于空值和无法截取整数的值,返回NaN。 + +```javascript +Math.trunc(NaN); // NaN +Math.trunc('foo'); // NaN +Math.trunc(); // NaN +``` -Math.sign方法用来判断一个数到底是正数、负数、还是零。如果参数为正数,返回+1;参数为负数,返回-1;参数为0,返回0;参数为NaN,返回NaN。 +对于没有部署这个方法的环境,可以用下面的代码模拟。 ```javascript +Math.trunc = Math.trunc || function(x) { + return x < 0 ? Math.ceil(x) : Math.floor(x); +} +``` + +### Math.sign() + +Math.sign方法用来判断一个数到底是正数、负数、还是零。 +它会返回五种值。 + +- 参数为正数,返回+1; +- 参数为负数,返回-1; +- 参数为0,返回0; +- 参数为-0,返回-0; +- 其他值,返回NaN。 + +```javascript Math.sign(-5) // -1 Math.sign(5) // +1 Math.sign(0) // +0 -Math.sign(-) // -0 +Math.sign(-0) // -0 Math.sign(NaN) // NaN +Math.sign('foo'); // NaN +Math.sign(); // NaN +``` +对于没有部署这个方法的环境,可以用下面的代码模拟。 + +```javascript +Math.sign = Math.sign || function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; +} ``` -ES5通过下面的代码,可以部署Math.sign()。 +### Math.cbrt() + +Math.cbrt方法用于计算一个数的立方根。 ```javascript +Math.cbrt(-1); // -1 +Math.cbrt(0); // 0 +Math.cbrt(1); // 1 +Math.cbrt(2); // 1.2599210498948734 +``` -(function (global) { - var isNaN = Number.isNaN; +对于没有部署这个方法的环境,可以用下面的代码模拟。 - Object.defineProperty(Math, 'sign', { - value: function sign(value) { - var n = +value; - if (isNaN(n)) - return n /* NaN */; +```javascript +Math.cbrt = Math.cbrt || function(x) { + var y = Math.pow(Math.abs(x), 1/3); + return x < 0 ? -y : y; +}; +``` - if (n === 0) - return n; // Keep the sign of the zero. +### Math.clz32() - return (n < 0) ? -1 : 1; - }, - configurable: true, - enumerable: false, - writable: true - }); -})(this); +JavaScript的整数使用32位二进制形式表示,Math.clz32方法返回一个数的32位无符号整数形式有多少个前导0。 +```javascript +Math.clz32(0) // 32 +Math.clz32(1) // 31 +Math.clz32(1000) // 22 ``` -**(3)数学方法** +上面代码中,0的二进制形式全为0,所以有32个前导0;1的二进制形式是0b1,只占1位,所以32位之中有31个前导0;1000的二进制形式是0b1111101000,一共有10位,所以32位之中有22个前导0。 -ES6在Math对象上还提供了许多新的数学方法。 +对于小数,Math.clz32方法只考虑整数部分。 -- Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine) -- Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine) -- Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent) -- Math.cbrt(x) 返回x的立方根 -- Math.clz32(x) 返回x的32位二进制整数表示形式的前导0的个数 +```javascript +Math.clz32(3.2) // 30 +Math.clz32(3.9) // 30 +``` + +对于空值或其他类型的值,Math.clz32方法会将它们先转为数值,然后再计算。 + +```javascript +Math.clz32() // 32 +Math.clz32(NaN) // 32 +Math.clz32(Infinity) // 32 +Math.clz32(null) // 32 +Math.clz32('foo') // 32 +Math.clz32([]) // 32 +Math.clz32({}) // 32 +Math.clz32(true) // 31 +``` + +### Math.imul() + +Math.imul方法返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数。 + +```javascript +Math.imul(2, 4); // 8 +Math.imul(-1, 8); // -8 +Math.imul(-2, -2); // 4 +``` + +如果只考虑最后32位(含第一个整数位),大多数情况下,`Math.imul(a, b)`与`a * b`的结果是相同的,即该方法等同于`(a * b)|0`的效果。之所以需要部署这个方法,是因为JavaScript有精度限制,超过2的53次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul方法可以返回正确的低位数值。 + +```javascript +(0x7fffffff * 0x7fffffff)|0 // 0 +``` + +上面这个乘法算式,返回结果为0。但是由于这两个数的个位数都是1,所以这个结果肯定是不正确的。这个错误就是因为它们的乘积超过了2的53次方,JavaScript无法保存额外的精度,就把低位的值都变成了0。Math.imul方法可以返回正确的值1。 + +```javascript +Math.imul(0x7fffffff, 0x7fffffff) // 1 +``` + +### Math.fround() + +Math.fround方法返回一个数的单精度浮点数形式。 + +```javascript +Math.fround(0); // 0 +Math.fround(1); // 1 +Math.fround(1.337); // 1.3370000123977661 +Math.fround(1.5); // 1.5 +Math.fround(NaN); // NaN +``` + +对于整数来说,Math.fround方法返回结果不会有任何不同,区别主要是那些无法用64个二进制位精确表示的小数。这时,Math.fround方法会返回最接近这个小数的单精度浮点数。 + +对于没有部署这个方法的环境,可以用下面的代码模拟。 + +```javascript +Math.fround = Math.fround || function(x) { + return new Float32Array([x])[0]; +}; +``` + +### Math.hypot() + +Math.hypot方法返回所有参数的平方和的平方根。 + +```javascript +Math.hypot(3, 4); // 5 +Math.hypot(3, 4, 5); // 7.0710678118654755 +Math.hypot(); // 0 +Math.hypot(NaN); // NaN +Math.hypot(3, 4, 'foo'); // NaN +Math.hypot(3, 4, '5'); // 7.0710678118654755 +Math.hypot(-3); // 3 +``` + +上面代码中,3的平方加上4的平方,等于5的平方。 + +如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回NaN。 + +### 对数方法 + +ES6新增了4个对数相关方法。 + +(1) Math.expm1() + +`Math.expm1(x)`返回ex - 1。 + +```javascript +Math.expm1(-1); // -0.6321205588285577 +Math.expm1(0); // 0 +Math.expm1(1); // 1.718281828459045 +``` + +对于没有部署这个方法的环境,可以用下面的代码模拟。 + +```javascript +Math.expm1 = Math.expm1 || function(x) { + return Math.exp(x) - 1; +}; +``` + +(2)Math.log1p() + +`Math.log1p(x)`方法返回1 + x的自然对数。如果x小于-1,返回NaN。 + +```javascript +Math.log1p(1); // 0.6931471805599453 +Math.log1p(0); // 0 +Math.log1p(-1); // -Infinity +Math.log1p(-2); // NaN +``` + +对于没有部署这个方法的环境,可以用下面的代码模拟。 + +```javascript +Math.log1p = Math.log1p || function(x) { + return Math.log(1 + x); +}; +``` + +(3)Math.log10() + +`Math.log10(x)`返回以10为底的x的对数。如果x小于0,则返回NaN。 + +```javascript +Math.log10(2); // 0.3010299956639812 +Math.log10(1); // 0 +Math.log10(0); // -Infinity +Math.log10(-2); // NaN +Math.log10(100000); // 5 +``` + +对于没有部署这个方法的环境,可以用下面的代码模拟。 + +```javascript +Math.log10 = Math.log10 || function(x) { + return Math.log(x) / Math.LN10; +}; +``` + +(4)Math.log2() + +`Math.log2(x)`返回以2为底的x的对数。如果x小于0,则返回NaN。 + +```javascript +Math.log2(3); // 1.584962500721156 +Math.log2(2); // 1 +Math.log2(1); // 0 +Math.log2(0); // -Infinity +Math.log2(-2); // NaN +Math.log2(1024); // 10 +``` + +对于没有部署这个方法的环境,可以用下面的代码模拟。 + +```javascript +Math.log2 = Math.log2 || function(x) { + return Math.log(x) / Math.LN2; +}; +``` + +### 三角函数方法 + +ES6新增了6个三角函数方法。 + +- Math.sinh(x) 返回x的双曲正弦(hyperbolic sine) - Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine) -- Math.expm1(x) 返回eˆx - 1 -- Math.fround(x) 返回x的单精度浮点数形式 -- Math.hypot(...values) 返回所有参数的平方和的平方根 -- Math.imul(x, y) 返回两个参数以32位整数形式相乘的结果 -- Math.log1p(x) 返回1 + x的自然对数 -- Math.log10(x) 返回以10为底的x的对数 -- Math.log2(x) 返回以2为底的x的对数 -- Math.tanh(x) 返回x的双曲正切(hyperbolic tangent) +- Math.tanh(x) 返回x的双曲正切(hyperbolic tangent) +- Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine) +- Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine) +- Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent) From 2589347bfb80bae79f49492cf36c719096d54b84 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Thu, 9 Jul 2015 08:04:45 +0800 Subject: [PATCH 0002/1267] edit Promise --- docs/promise.md | 150 +++++++++++++++++++++++++--------------------- docs/reference.md | 1 + 2 files changed, 82 insertions(+), 69 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index dd573bb8e..4f70c0545 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -1,24 +1,48 @@ # Promise对象 -## 基本用法 +## Promise的含义 Promise在JavaScript语言早有实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。 所谓Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API,可供进一步处理。 -有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供的接口,使得控制异步操作更加容易。 +Promise对象有以下两个特点。 + +(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 + +(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 + +有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。 + +Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 + +如果某些事件不断地反复发生,一般来说,使用stream模式是比部署Promise更好的选择。 + +## 基本用法 ES6规定,Promise对象是一个构造函数,用来生成Promise实例。 +下面代码创造了一个Promise实例。 + ```javascript var promise = new Promise(function(resolve, reject) { + // ... some code + if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } }); +``` + +Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。 + +resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。 + +Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。 +```javascript promise.then(function(value) { // success }, function(value) { @@ -26,32 +50,27 @@ promise.then(function(value) { }); ``` -上面代码中,Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve方法和reject方法。如果异步操作成功,则用resolve方法将Promise对象的状态,从“未完成”变为“成功”(即从pending变为resolved);如果异步操作失败,则用reject方法将Promise对象的状态,从“未完成”变为“失败”(即从pending变为rejected)。 - -Promise实例生成以后,可以用then方法分别指定resolve方法和reject方法的回调函数。 +then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。 -下面是一个使用Promise对象的简单例子。 +下面是一个Promise对象的简单例子。 ```javascript - function timeout(ms) { return new Promise((resolve) => { - setTimeout(resolve, ms); + setTimeout(resolve, ms, 'done'); }); } -timeout(100).then(() => { - console.log('done'); +timeout(100).then((value) => { + console.log(value); }); - ``` -上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。一旦Promise对象的状态变为resolved,就会触发then方法绑定的回调函数。 +上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为Resolved,就会触发then方法绑定的回调函数。 下面是一个用Promise对象实现的Ajax操作的例子。 ```javascript - var getJSON = function(url) { var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); @@ -78,12 +97,11 @@ getJSON("/posts.json").then(function(json) { }, function(error) { console.error('出错了', error); }); - ``` -上面代码中,getJSON是对XMLHttpRequest对象的封装,用于发出一个针对JSON数据的HTTP请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve方法和reject方法调用时,都带有参数。 +上面代码中,getJSON是对XMLHttpRequest对象的封装,用于发出一个针对JSON数据的HTTP请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。 -如果调用resolve方法和reject方法时带有参数,那么它们的参数会被传递给回调函数。reject方法的参数通常是Error对象的实例,表示抛出的错误;resolve方法的参数除了正常的值以外,还可能是另一个Promise实例,表示异步操作的结果有可能是一个值,也有可能是另一个异步操作,比如像下面这样。 +如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例,表示异步操作的结果有可能是一个值,也有可能是另一个异步操作,比如像下面这样。 ```javascript var p1 = new Promise(function(resolve, reject){ @@ -96,67 +114,84 @@ var p2 = new Promise(function(resolve, reject){ }) ``` -上面代码中,p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,p1的状态就会传递给p2。 +上面代码中,p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。 -注意,这时p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是fulfilled或者rejected,那么p2的回调函数将会立刻执行。 +注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是Pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是Resolved或者Rejected,那么p2的回调函数将会立刻执行。 ## Promise.prototype.then() -Promise.prototype.then方法返回的是一个新的Promise对象,因此可以采用链式写法,即then方法后面再调用另一个then方法。 +Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。 -```javascript +then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。 +```javascript getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... }); - ``` 上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。 -如果前一个回调函数返回的是Promise对象,这时后一个回调函数就会等待该Promise对象有了运行结果,才会进一步调用。 +采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。 ```javascript - getJSON("/post/1.json").then(function(post) { return getJSON(post.commentURL); -}).then(function(comments) { - // ... +}).then(function funcA(comments) { + console.log("Resolved: ", comments); +}, function funcB(err){ + console.log("Rejected: ", err); }); - ``` -then方法还可以接受第二个参数,表示Promise对象的状态变为rejected时的回调函数。 +上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为Resolved,就调用funcA,如果状态变为Rejected,就调用funcB。 + +如果采用箭头函数,上面的代码可以写得更简洁。 + +```javascript +getJSON("/post/1.json").then( + post => getJSON(post.commentURL) +).then( + comments => console.log("Resolved: ", comments), + err => console.log("Rejected: ", err) +); +``` ## Promise.prototype.catch() -Promise.prototype.catch方法是`Promise.prototype.then(null, rejection)`的别名,用于指定发生错误时的回调函数。 +Promise.prototype.catch方法是`.then(null, rejection)`的别名,用于指定发生错误时的回调函数。 ```javascript - getJSON("/posts.json").then(function(posts) { // ... }).catch(function(error) { // 处理前一个回调函数运行时发生的错误 console.log('发生错误!', error); }); - ``` -上面代码中,getJSON方法返回一个Promise对象,如果该对象运行正常,则会调用then方法指定的回调函数;如果该方法抛出错误,则会调用catch方法指定的回调函数,处理这个错误。 +上面代码中,getJSON方法返回一个Promise对象,如果该对象状态变为Resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数,处理这个错误。 + +```javascript +p.then((val) => console.log("fulfilled:", val)) + .catch((err) => console.log("rejected:", err)); + +// 等同于 + +p.then((val) => console.log(fulfilled:", val)) + .then(null, (err) => console.log("rejected:", err)); +``` 下面是一个例子。 ```javascript - var promise = new Promise(function(resolve, reject) { throw new Error('test') }); promise.catch(function(error) { console.log(error) }); // Error: test - ``` 上面代码中,Promise抛出一个错误,就被catch方法指定的回调函数捕获。 @@ -164,7 +199,6 @@ promise.catch(function(error) { console.log(error) }); 如果Promise状态已经变成resolved,再抛出错误是无效的。 ```javascript - var promise = new Promise(function(resolve, reject) { resolve("ok"); throw new Error('test'); @@ -173,7 +207,6 @@ promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) }); // ok - ``` 上面代码中,Promise在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。 @@ -181,7 +214,6 @@ promise Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。 ```javascript - getJSON("/post/1.json").then(function(post) { return getJSON(post.commentURL); }).then(function(comments) { @@ -189,7 +221,6 @@ getJSON("/post/1.json").then(function(post) { }).catch(function(error) { // 处理前面三个Promise产生的错误 }); - ``` 上面代码中,一共有三个Promise对象:一个由getJSON产生,两个由then产生。它们之中任何一个抛出的错误,都会被最后一个catch捕获。 @@ -197,7 +228,6 @@ getJSON("/post/1.json").then(function(post) { 跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。 ```javascript - var someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 @@ -208,13 +238,11 @@ var someAsyncThing = function() { someAsyncThing().then(function() { console.log('everything is great'); }); - ``` 上面代码中,someAsyncThing函数产生的Promise对象会报错,但是由于没有调用catch方法,这个错误不会被捕获,也不会传递到外层代码,导致运行后没有任何输出。 ```javascript - var promise = new Promise(function(resolve, reject) { resolve("ok"); setTimeout(function() { throw new Error('test') }, 0) @@ -222,7 +250,6 @@ var promise = new Promise(function(resolve, reject) { promise.then(function(value) { console.log(value) }); // ok // Uncaught Error: test - ``` 上面代码中,Promise指定在下一轮“事件循环”再抛出错误,结果由于没有指定catch语句,就冒泡到最外层,成了未捕获的错误。 @@ -240,7 +267,6 @@ process.on('unhandledRejection', function (err, p) { 需要注意的是,catch方法返回的还是一个Promise对象,因此后面还可以接着调用then方法。 ```javascript - var someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 @@ -257,7 +283,6 @@ someAsyncThing().then(function() { }); // oh no [ReferenceError: x is not defined] // carry on - ``` 上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。 @@ -265,7 +290,6 @@ someAsyncThing().then(function() { catch方法之中,还能再抛出错误。 ```javascript - var someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 @@ -283,13 +307,11 @@ someAsyncThing().then(function() { console.log('carry on'); }); // oh no [ReferenceError: x is not defined] - ``` -上面代码中,catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会到传递到外层。如果改写一下,结果就不一样了。 +上面代码中,catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会传递到外层。如果改写一下,结果就不一样了。 ```javascript - someAsyncThing().then(function() { return someOtherAsyncThing(); }).catch(function(error) { @@ -301,19 +323,16 @@ someAsyncThing().then(function() { }); // oh no [ReferenceError: x is not defined] // carry on [ReferenceError: y is not defined] - ``` 上面代码中,第二个catch方法用来捕获,前一个catch方法抛出的错误。 -## Promise.all(),Promise.race() +## Promise.all() Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。 ```javascript - var p = Promise.all([p1,p2,p3]); - ``` 上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例。(Promise.all方法的参数不一定是数组,但是必须具有iterator接口,且返回的每个成员都是Promise实例。) @@ -327,74 +346,67 @@ p的状态由p1、p2、p3决定,分成两种情况。 下面是一个具体的例子。 ```javascript - // 生成一个Promise对象的数组 var promises = [2, 3, 5, 7, 11, 13].map(function(id){ return getJSON("/post/" + id + ".json"); }); Promise.all(promises).then(function(posts) { - // ... + // ... }).catch(function(reason){ // ... }); - ``` +## Promise.race() Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。 ```javascript - var p = Promise.race([p1,p2,p3]); - ``` -上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。 +上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。 如果Promise.all方法和Promise.race方法的参数,不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。 -## Promise.resolve(),Promise.reject() +## Promise.resolve() 有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。 ```javascript - var jsPromise = Promise.resolve($.ajax('/whatever.json')); - ``` -上面代码将jQuery生成deferred对象,转为一个新的ES6的Promise对象。 +上面代码将jQuery生成deferred对象,转为一个新的Promise对象。 -如果Promise.resolve方法的参数,不是具有then方法的对象(又称thenable对象),则返回一个新的Promise对象,且它的状态为fulfilled。 +如果Promise.resolve方法的参数,不是具有then方法的对象(又称thenable对象),则返回一个新的Promise对象,且它的状态为Resolved。 ```javascript - var p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) }); // Hello - ``` -上面代码生成一个新的Promise对象的实例p,它的状态为fulfilled,所以回调函数会立即执行,Promise.resolve方法的参数就是回调函数的参数。 +上面代码生成一个新的Promise对象的实例p。由于字符串Hello不属于异步操作(判断方法是它不是具有then方法的对象),返回Promise实例的状态从一生成就是Resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。 -所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve方法。 +Promise.resolve方法允许调用时不带参数。所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve方法。 ```javascript - var p = Promise.resolve(); p.then(function () { // ... }); - ``` 上面代码的变量p就是一个Promise对象。 -如果Promise.resolve方法的参数是一个Promise对象的实例,则会被原封不动地返回。 +如果Promise.resolve方法的参数是一个Promise实例,则会被原封不动地返回。 + +## Promise.reject() Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。Promise.reject方法的参数reason,会被传递给实例的回调函数。 diff --git a/docs/reference.md b/docs/reference.md index 71a0c39b5..d62ad419b 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -101,6 +101,7 @@ - Jake Archibald, [JavaScript Promises: There and back again](http://www.html5rocks.com/en/tutorials/es6/promises/) - Tilde, [rsvp.js](https://github.com/tildeio/rsvp.js) - Sandeep Panda, [An Overview of JavaScript Promises](http://www.sitepoint.com/overview-javascript-promises/): ES6 Promise入门介绍 +- Dave Atchley, [ES6 Promises](http://www.datchley.name/es6-promises/): 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方法的例子 From 004ad1717f298416b7a6e1aba921ec42920be89b Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sat, 11 Jul 2015 22:31:33 +0800 Subject: [PATCH 0003/1267] edit class/decorator --- docs/class.md | 170 +++++++++++++++++++++++++++++++++++++++++++++- docs/reference.md | 1 + 2 files changed, 168 insertions(+), 3 deletions(-) diff --git a/docs/class.md b/docs/class.md index fa2efab1a..e47a64c72 100644 --- a/docs/class.md +++ b/docs/class.md @@ -823,7 +823,11 @@ var y = new Rectangle(3, 4); // 正确 ## 修饰器 -修饰器(Decorator)用于修改类的行为。这是ES7的一个[提案](https://github.com/wycats/javascript-decorators),目前Babel转码器已经支持。 +### 类的修饰 + +修饰器(Decorator)是一个表达式,用来修改类的行为。这是ES7的一个[提案](https://github.com/wycats/javascript-decorators),目前Babel转码器已经支持。 + +修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。 ```javascript function testable(target) { @@ -838,7 +842,7 @@ console.log(MyTestableClass.isTestable) // true 上面代码中,`@testable`就是一个修饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。 -修饰器函数的参数,就是所要修饰的目标对象。比如上面代码中,testable函数的参数target,就是所要修饰的对象。如果希望修饰器的行为,能够根据目标对象的不同而不同,就要在外面再封装一层函数。 +修饰器函数可以接受三个参数,依次是目标函数、属性名和该属性的描述对象。后两个参数可省略。上面代码中,testable函数的参数target,就是所要修饰的对象。如果希望修饰器的行为,能够根据目标对象的不同而不同,就要在外面再封装一层函数。 ```javascript function testable(isTestable) { @@ -887,7 +891,7 @@ export function mixins(...list) { import { mixins } from './mixins' const Foo = { - foo() { console.log('foo') } + foo() { console.log('foo') } } @mixins(Foo) @@ -915,6 +919,8 @@ let obj = new MyClass(); obj.foo() // 'foo' ``` +### 方法的修饰 + 修饰器不仅可以修饰类,还可以修饰类的属性。 ```javascript @@ -960,3 +966,161 @@ function nonenumerable(target, name, descriptor) { } ``` +修饰器有注释的作用。 + +```javascript +@testable +class Person { + @readonly + @nonenumerable + name() { return `${this.first} ${this.last}` } +} +``` + +从上面代码中,我们一眼就能看出,MyTestableClass类是可测试的,而name方法是只读和不可枚举的。 + +除了注释,修饰器还能用来类型检查。所以,对于Class来说,这项功能相当有用。从长期来看,它将是JavaScript代码静态分析的重要工具。 + +### core-decorators.js + +[core-decorators.js](https://github.com/jayphelps/core-decorators.js)提供了几个常见的修饰器。 + +(1)@autobind + +autobind修饰器使得方法中的this对象,绑定原始对象。 + +```javascript +import { autobind } from 'core-decorators'; + +class Person { + @autobind + getPerson() { + return this; + } +} + +let person = new Person(); +let getPerson = person.getPerson; + +getPerson() === person; +// true +``` + +(2)@readonly + +readonly修饰器是的属性或方法不可写。 + +```javascript +import { readonly } from 'core-decorators'; + +class Meal { + @readonly + entree = 'steak'; +} + +var dinner = new Meal(); +dinner.entree = 'salmon'; +// Cannot assign to read only property 'entree' of [object Object] +``` + +(3)@override + +override修饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。 + +```javascript +import { override } from 'core-decorators'; + +class Parent { + speak(first, second) {} +} + +class Child extends Parent { + @override + speak() {} + // SyntaxError: Child#speak() does not properly override Parent#speak(first, second) +} + +// or + +class Child extends Parent { + @override + speaks() {} + // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain. + // + // Did you mean "speak"? +} +``` + +(4)@deprecate (别名@deprecated) + +deprecate或deprecated修饰器在控制台显示一条警告,表示该方法将废除。 + +```javascript +import { deprecate } from 'core-decorators'; + +class Person { + @deprecate + facepalm() {} + + @deprecate('We stopped facepalming') + facepalmHard() {} + + @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' }) + facepalmHarder() {} +} + +let person = new Person(); + +person.facepalm(); +// DEPRECATION Person#facepalm: This function will be removed in future versions. + +person.facepalmHard(); +// DEPRECATION Person#facepalmHard: We stopped facepalming + +person.facepalmHarder(); +// DEPRECATION Person#facepalmHarder: We stopped facepalming +// +// See http://knowyourmeme.com/memes/facepalm for more details. +// +``` + +(5)@suppressWarnings + +suppressWarnings修饰器抑制decorated修饰器导致的`console.warn()`调用。但是,异步代码出发的调用除外。 + +```javascript +import { suppressWarnings } from 'core-decorators'; + +class Person { + @deprecated + facepalm() {} + + @suppressWarnings + facepalmWithoutWarning() { + this.facepalm(); + } +} + +let person = new Person(); + +person.facepalmWithoutWarning(); +// no warning is logged +``` + +### Babel转码器的支持 + +目前,Babel转码器已经支持Decorator,命令行的用法如下。 + +```bash +$ babel --optional es7.decorators +``` + +脚本中打开的命令如下。 + +```javascript +babel.transfrom("code", {optional: ["es7.decorators"]}) +``` + +Babel的官方网站提供一个[在线转码器](https://babeljs.io/repl/),只要勾选Experimental,就能支持Decorator的在线转码。 + + diff --git a/docs/reference.md b/docs/reference.md index d62ad419b..a630f7705 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -119,6 +119,7 @@ - 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语法的详细介绍和设计思想分析 - Maximiliano Fierro, [Declarative vs Imperative](http://elmasse.github.io/js/decorators-bindings-es7.html): Decorators介绍 +- Addy Osmani, [Exploring ES2016 Decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841): Decorator的深入介绍 ## 工具 From 7232ad80614d64ae7f57cf8f1c9ff6c997061aca Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 12 Jul 2015 20:08:11 +0800 Subject: [PATCH 0004/1267] edit generator --- docs/generator.md | 132 +++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 78 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index 6762b97a0..5ccb29ed5 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -8,7 +8,7 @@ Generator函数是ES6提供的一种异步编程解决方案,语法行为与 Generator函数有多种理解角度。从语法上,首先可以把它理解成一个函数的内部状态的遍历器(也就是说,Generator函数是一个状态机)。它每调用一次,就进入下一个内部状态。Generator函数可以控制内部状态的变化,依次遍历这些状态。 -在形式上,Generator函数是一个普通函数,但是有两个特征。一是,function命令与函数名之间有一个星号;二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态(yield语句在英语里的意思就是“产出”)。 +形式上,Generator函数是一个普通函数,但是有两个特征。一是,function命令与函数名之间有一个星号;二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态(yield语句在英语里的意思就是“产出”)。 ```javascript function* helloWorldGenerator() { @@ -236,7 +236,6 @@ it.next(13) for...of循环可以自动遍历Generator函数,且此时不再需要调用next方法。 ```javascript - function *foo() { yield 1; yield 2; @@ -250,7 +249,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循环之中。 @@ -258,7 +256,6 @@ for (let v of foo()) { 下面是一个利用generator函数和for...of循环,实现斐波那契数列的例子。 ```javascript - function* fibonacci() { let [prev, curr] = [0, 1]; for (;;) { @@ -271,7 +268,6 @@ for (let n of fibonacci()) { if (n > 1000) break; console.log(n); } - ``` 从上面代码可见,使用for...of语句时不需要使用next方法。 @@ -513,7 +509,6 @@ log(g()); 如果yield命令后面跟的是一个遍历器,需要在yield命令后面加上星号,表明它返回的是一个遍历器。这被称为yield*语句。 ```javascript - let delegatedIterator = (function* () { yield 'Hello!'; yield 'Bye!'; @@ -532,11 +527,32 @@ for(let value of delegatingIterator) { // "Hello!" // "Bye!" // "Ok, bye." - ``` 上面代码中,delegatingIterator是代理者,delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Genertor函数,有递归的效果。 +yield*语句等同于在Generator函数内部,部署一个for...of循环。 + +```javascript +function* concat(iter1, iter2) { + yield* iter1; + yield* iter2; +} + +// 等同于 + +function* concat(iter1, iter2) { + for (var value of iter1) { + yield value; + } + for (var value of iter2) { + yield value; + } +} +``` + +上面代码说明,yield*不过是for...of的一种简写形式,完全可以用后者替代前者。 + 再来看一个对比的例子。 ```javascript @@ -572,13 +588,11 @@ gen.next() // -> 'close' 如果`yield*`后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。 ```javascript - function* gen(){ yield* ["a", "b", "c"]; } gen().next() // { value:"a", done:false } - ``` 上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器。 @@ -586,7 +600,6 @@ gen().next() // { value:"a", done:false } 如果被代理的Generator函数有return语句,那么就可以向代理它的Generator函数返回数据。 ```javascript - function *foo() { yield 2; yield 3; @@ -607,7 +620,6 @@ it.next(); // it.next(); // it.next(); // "v: foo" it.next(); // - ``` 上面代码在第四次调用next方法的时候,屏幕上会有输出,这是因为函数foo的return语句,向函数bar提供了返回值。 @@ -615,7 +627,6 @@ it.next(); // `yield*`命令可以很方便地取出嵌套数组的所有成员。 ```javascript - function* iterTree(tree) { if (Array.isArray(tree)) { for(let i=0; i < tree.length; i++) { @@ -636,13 +647,11 @@ for(let x of iterTree(tree)) { // c // d // e - ``` 下面是一个稍微复杂的例子,使用yield*语句遍历完全二叉树。 ```javascript - // 下面是二叉树的构造函数, // 三个参数分别是左树、当前节点和右树 function Tree(left, label, right) { @@ -678,7 +687,6 @@ for (let node of inorder(tree)) { result // ['a', 'b', 'c', 'd', 'e', 'f', 'g'] - ``` ## 作为对象属性的Generator函数 @@ -724,7 +732,7 @@ console.log(...squared); // 0 1 4 9 16 25 ``` -“推导”这种语法结构,在ES6只能用于数组,ES7将其推广到了Generator函数。for...of循环会自动调用遍历器的next方法,将返回值的value属性作为数组的一个成员。 +“推导”这种语法结构,不仅可以用于数组,ES7将其推广到了Generator函数。for...of循环会自动调用遍历器的next方法,将返回值的value属性作为数组的一个成员。 Generator函数推导是对数组结构的一种模拟,它的最大优点是惰性求值,即直到真正用到时才会求值,这样可以保证效率。请看下面的例子。 @@ -759,7 +767,6 @@ console.log(squared.next()); Generator是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。 ```javascript - var ticking = true; var clock = function() { if (ticking) @@ -768,13 +775,11 @@ var clock = function() { console.log('Tock!'); ticking = !ticking; } - ``` 上面代码的clock函数一共有两种状态(Tick和Tock),每运行一次,就改变一次状态。这个函数如果用Generator实现,就是下面这样。 ```javascript - var clock = function*(_) { while (true) { yield _; @@ -783,7 +788,6 @@ var clock = function*(_) { console.log('Tock!'); } }; - ``` 上面的Generator实现与ES5实现对比,可以看到少了用来保存状态的外部变量ticking,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。 @@ -817,19 +821,17 @@ Generator可以暂停函数执行,返回任意表达式的值。这种特点 Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。 ```javascript - -function* loadUI() { - showLoadingScreen(); - yield loadUIDataAsynchronously(); - hideLoadingScreen(); -} +function* loadUI() { + showLoadingScreen(); + yield loadUIDataAsynchronously(); + hideLoadingScreen(); +} var loader = loadUI(); // 加载UI -loader.next() +loader.next() // 卸载UI loader.next() - ``` 上面代码表示,第一次调用loadUI函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next方法,则会显示Loading界面,并且异步加载数据。等到数据加载完成,再一次使用next方法,则会隐藏Loading界面。可以看到,这种写法的好处是所有Loading界面的逻辑,都被封装在一个函数,按部就班非常清晰。 @@ -837,7 +839,6 @@ loader.next() Ajax是典型的异步操作,通过Generator函数部署Ajax操作,可以用同步的方式表达。 ```javascript - function* main() { var result = yield request("http://some.url"); var resp = JSON.parse(result); @@ -852,7 +853,6 @@ function request(url) { var it = main(); it.next(); - ``` 上面代码的main函数,就是通过Ajax操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield语句构成的表达式,本身是没有值的,总是等于undefined。 @@ -860,18 +860,16 @@ it.next(); 下面是另一个例子,通过Generator函数逐行读取文本文件。 ```javascript - function* numbers() { - let file = new FileReader("numbers.txt"); - try { - while(!file.eof) { - yield parseInt(file.readLine(), 10); - } - } finally { - file.close(); - } + let file = new FileReader("numbers.txt"); + try { + while(!file.eof) { + yield parseInt(file.readLine(), 10); + } + } finally { + file.close(); + } } - ``` 上面代码打开文本文件,使用yield语句可以手动逐行读取文件。 @@ -881,7 +879,6 @@ function* numbers() { 如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。 ```javascript - step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) { @@ -891,32 +888,28 @@ step1(function (value1) { }); }); }); - ``` 采用Promise改写上面的代码。 ```javascript - Q.fcall(step1) -.then(step2) -.then(step3) -.then(step4) -.then(function (value4) { + .then(step2) + .then(step3) + .then(step4) + .then(function (value4) { // Do something with value4 -}, function (error) { + }, function (error) { // Handle any error from step1 through step4 -}) -.done(); - + }) + .done(); ``` 上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量Promise的语法。Generator函数可以进一步改善代码运行流程。 ```javascript - function* longRunningTask() { - try { + try { var value1 = yield step1(); var value2 = yield step2(value1); var value3 = yield step3(value2); @@ -926,13 +919,11 @@ function* longRunningTask() { // Handle any error from step1 through step4 } } - ``` 然后,使用一个函数,按次序自动执行所有步骤。 ```javascript - scheduler(longRunningTask()); function scheduler(task) { @@ -945,15 +936,13 @@ function scheduler(task) { } }, 0); } - ``` 注意,yield语句是同步运行,不是异步运行(否则就失去了取代回调函数的设计目的了)。实际操作中,一般让yield语句返回Promise对象。 ```javascript - var Q = require('q'); - + function delay(milliseconds) { var deferred = Q.defer(); setTimeout(deferred.resolve, milliseconds); @@ -963,7 +952,6 @@ function delay(milliseconds) { function* f(){ yield delay(100); }; - ``` 上面代码使用Promise的函数库Q,yield语句返回的就是一个Promise对象。 @@ -971,7 +959,6 @@ function* f(){ 多个任务按顺序一个接一个执行时,yield语句可以按顺序排列。多个任务需要并列执行时(比如只有A任务和B任务都执行完,才能执行C任务),可以采用数组的写法。 ```javascript - function* parallelDownloads() { let [text1,text2] = yield [ taskA(), @@ -979,7 +966,6 @@ function* parallelDownloads() { ]; console.log(text1, text2); } - ``` 上面代码中,yield语句的参数是一个数组,成员就是两个任务taskA和taskB,只有等这两个任务都完成了,才会接着执行下面的语句。 @@ -989,24 +975,22 @@ function* parallelDownloads() { 利用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 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); + console.log(key, value); } // foo 3 // bar 7 - ``` 上述代码中,myObj是一个普通对象,通过iterEntries函数,就有了iterator接口。也就是说,可以在任意对象上部署next方法。 @@ -1014,10 +998,9 @@ for (let [key, value] of iterEntries(myObj)) { 下面是一个对数组部署Iterator接口的例子,尽管数组原生具有这个接口。 ```javascript - function* makeSimpleGenerator(array){ var nextIndex = 0; - + while(nextIndex < array.length){ yield array[nextIndex++]; } @@ -1028,7 +1011,6 @@ var gen = makeSimpleGenerator(['yo', 'ya']); gen.next().value // 'yo' gen.next().value // 'ya' gen.next().done // true - ``` ### (4)作为数据结构 @@ -1036,29 +1018,24 @@ gen.next().done // true Generator可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为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'), @@ -1066,7 +1043,6 @@ function doStuff() { fs.readFile.bind(null, 'and-such.txt') ]; } - ``` 上面的函数,可以用一模一样的for...of循环处理!两相一比较,就不难看出Generator使得数据或者操作,具备了类似数组的接口。 From 01340b7e41d80eb7eb3024e7def0b3d4fbbd4abc Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Thu, 16 Jul 2015 15:38:31 +0800 Subject: [PATCH 0005/1267] edit module/export default --- docs/module.md | 71 +++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/docs/module.md b/docs/module.md index 123ee1fcb..6de58fee3 100644 --- a/docs/module.md +++ b/docs/module.md @@ -24,7 +24,7 @@ import { stat, exists, readFile } from 'fs'; 模块功能主要由两个命令构成:export和import。export命令用于用户自定义模块,规定对外接口;import命令用于输入其他模块提供的功能,同时创造命名空间(namespace),防止函数名冲突。 -ES6允许将独立的JS文件作为模块,也就是说,允许一个JavaScript脚本文件调用另一个脚本文件。该文件内部的所有变量,外部无法获取,必须使用export关键字输出变量。下面是一个JS文件,里面使用export关键字输出变量。 +ES6允许将独立的JS文件作为模块,也就是说,允许一个JavaScript脚本文件调用另一个脚本文件。该文件内部的所有变量,外部无法获取,必须使用export关键字输出变量。下面是一个JS文件,里面使用export命令输出变量。 ```javascript // profile.js @@ -48,6 +48,16 @@ export {firstName, lastName, year}; 上面代码在export命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在var语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。 +export命令除了输出变量,还可以输出函数或类(class)。 + +```javascript +export function multiply (x, y) { + return x * y; +}; +``` + +上面代码对外输出一个函数multiply。 + ## import命令 使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。 @@ -100,10 +110,9 @@ export default es6; ## 模块的整体输入 -export命令除了输出变量,还可以输出方法或类(class)。下面是一个circle.js文件,它输出两个方法area和circumference。 +下面是一个circle.js文件,它输出两个方法area和circumference。 ```javascript - // circle.js export function area(radius) { @@ -113,31 +122,26 @@ export function area(radius) { export function circumference(radius) { return 2 * Math.PI * radius; } - ``` -然后,main.js输入circlek.js模块。 +然后,main.js文件输入circlek.js模块。 ```javascript - // main.js import { area, circumference } from 'circle'; console.log("圆面积:" + area(4)); console.log("圆周长:" + circumference(14)); - ``` 上面写法是逐一指定要输入的方法。另一种写法是整体输入。 ```javascript - import * as circle from 'circle'; console.log("圆面积:" + circle.area(4)); console.log("圆周长:" + circle.circumference(14)); - ``` ## module命令 @@ -157,7 +161,9 @@ module命令后面跟一个变量,表示输入的模块定义在该变量上 ## export default命令 -为了给用户提供方便,有时我们希望,用户不用知道输入哪个方法,就能加载模块。这时就要用到`export default`命令,为所要加载的模块指定默认输出。 +从前面的例子可以看出,使用import的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。 + +为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到`export default`命令,为模块指定默认输出。 ```javascript // export-default.js @@ -178,18 +184,6 @@ customName(); // 'foo' 上面代码的import命令,可以用任意名称指向`export-default.js`输出的方法。需要注意的是,这时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 @@ -209,8 +203,33 @@ export default foo; 上面代码中,foo函数的函数名foo,在模块外部是无效的。加载的时候,视同匿名函数加载。 +下面比较一下默认输出和正常输出。 + +```javascript +import crc32 from 'crc32'; +// 对应的输出 +export default function crc32(){} + +import { crc32 } from 'crc32'; +// 对应的输出 +export function crc32(){}; +``` + +上面代码的两组写法,第一组是使用`export default`时,对应的import语句不需要使用大括号;第二组是不使用`export default`时,对应的import语句需要使用大括号。 + `export default`命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此`export deault`命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能对应一个方法。 +本质上,`export default`就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。 + +```javascript +// modules.js +export default function (x, y) { + return x * y; +}; +// app.js +import { default } from 'modules'; +``` + 有了`export default`命令,输入模块时就非常直观了,以输入jQuery模块为例。 ```javascript @@ -263,11 +282,9 @@ export default function(x) { 这时,也可以将circle的属性或方法,改名后再输出。 ```javascript - // circleplus.js -export { area as circleArea } from 'circle'; - +export { area as circleArea } from 'circle'; ``` 上面代码表示,只输出circle模块的area方法,且将其改名为circleArea。 @@ -275,13 +292,11 @@ export { area as circleArea } from 'circle'; 加载上面模块的写法如下。 ```javascript - // main.js module math from "circleplus"; import exp from "circleplus"; console.log(exp(math.pi)); - ``` 上面代码中的"import exp"表示,将circleplus模块的默认方法加载为exp方法。 @@ -309,9 +324,7 @@ $ compile-modules convert file1.js file2.js o参数可以指定转码后的文件名。 ```bash - $ compile-modules convert -o out.js file1.js - ``` ### SystemJS From 89fc7766fb536bcbeef1ad25c4f4e7f9ce46c077 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 17 Jul 2015 08:03:45 +0800 Subject: [PATCH 0006/1267] edit async --- docs/array.md | 30 +-- docs/async.md | 517 +++++++++++++++++++++++++++++++++++++++++++++++- docs/intro.md | 4 +- docs/promise.md | 339 +------------------------------ 4 files changed, 522 insertions(+), 368 deletions(-) diff --git a/docs/array.md b/docs/array.md index 43c1a64cf..94ad0429c 100644 --- a/docs/array.md +++ b/docs/array.md @@ -5,13 +5,11 @@ Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。 ```javascript - let ps = document.querySelectorAll('p'); Array.from(ps).forEach(function (p) { console.log(p); }); - ``` 上面代码中,querySelectorAll方法返回的是一个类似数组的对象,只有将这个对象转为真正的数组,才能使用forEach方法。 @@ -19,42 +17,34 @@ Array.from(ps).forEach(function (p) { 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.prototyp.slice方法替代。 ```javascript - const toArray = (() => Array.from ? Array.from : obj => [].slice.call(obj) )(); - ``` Array.from()还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理。 ```JavaScript - Array.from(arrayLike, x => x * x); // 等同于 Array.from(arrayLike).map(x => x * x); - ``` 下面的例子将数组中布尔值为false的成员转为0。 @@ -77,11 +67,9 @@ function countSymbols(string) { Array.of方法用于将一组值,转换为数组。 ```javaScript - Array.of(3, 11, 8) // [3,11,8] Array.of(3) // [3] Array.of(3).length // 1 - ``` 这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。 @@ -124,11 +112,9 @@ console.log("found:", found); 数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。 ```javascript - [1, 5, 10, 15].findIndex(function(value, index, arr) { return value > 9; }) // 2 - ``` 这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。 @@ -150,13 +136,11 @@ console.log("found:", found); fill()使用给定值,填充一个数组。 ```javascript - ['a', 'b', 'c'].fill(7) // [7, 7, 7] new Array(3).fill(7) // [7, 7, 7] - ``` 上面代码表明,fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。 @@ -201,38 +185,32 @@ for (let [index, elem] of ['a', 'b'].entries()) { 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 - ``` ## 数组推导 -数组推导(array comprehension)提供简洁写法,允许直接通过现有数组生成新数组。这项功能没有被列入ES6,而是推迟到了ES7。 +数组推导(array comprehension)提供简洁写法,允许直接通过现有数组生成新数组。这项功能本来是要放入ES6的,但是TC39委员会想继续完善这项功能,让其支持所有数据结构(内部调用iterator对象),不像现在只支持数组,所以就把它推迟到了ES7。Babel转码器已经支持这个功能。 ```javascript var a1 = [1, 2, 3, 4]; @@ -265,7 +243,6 @@ var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ]; 数组推导可以替代map和filter方法。 ```javascript - [for (i of [1, 2, 3]) i * i]; // 等价于 [1, 2, 3].map(function (i) { return i * i }); @@ -273,7 +250,6 @@ var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ]; [for (i of [1,4,2,3,-8]) if (i < 3) i]; // 等价于 [1,4,2,3,-8].filter(function(i) { return i < 3 }); - ``` 上面代码说明,模拟map功能只要单纯的for...of循环就行了,模拟filter功能除了for...of循环,还必须加上if语句。 @@ -281,7 +257,6 @@ var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ]; 在一个数组推导中,还可以使用多个for...of结构,构成多重循环。 ```javascript - var a1 = ["x1", "y1"]; var a2 = ["x2", "y2"]; var a3 = ["x3", "y3"]; @@ -295,7 +270,6 @@ var a3 = ["x3", "y3"]; // y1x2y3 // y1y2x3 // y1y2y3 - ``` 上面代码在一个数组推导之中,使用了三个for...of结构。 @@ -305,11 +279,9 @@ var a3 = ["x3", "y3"]; 由于字符串可以视为数组,因此字符串也可以直接用于数组推导。 ```javascript - [for (c of 'abcde') if (/[aeiou]/.test(c)) c].join('') // 'ae' [for (c of 'abcde') c+'0'].join('') // 'a0b0c0d0e0' - ``` 上面代码使用了数组推导,对字符串进行处理。 diff --git a/docs/async.md b/docs/async.md index 529b6e7f6..1bf0424fb 100644 --- a/docs/async.md +++ b/docs/async.md @@ -466,9 +466,217 @@ run(gen); Thunk函数并不是Generator函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制Generator函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。 -## co函数库 +## co模块 -如果并发执行异步操作,可以将异步操作都放入一个数组,跟在yield语句后面。 +### 基本用法 + +[co模块](https://github.com/tj/co)是著名程序员TJ Holowaychuk于2013年6月发布的一个小工具,用于Generator函数的自动执行。 + +比如,有一个Generator函数,用于依次读取两个文件。 + +```javascript +var gen = function* (){ + var f1 = yield readFile('/etc/fstab'); + var f2 = yield readFile('/etc/shells'); + console.log(f1.toString()); + console.log(f2.toString()); +}; +``` + +co模块可以让你不用编写Generator函数的执行器。 + +```javascript +var co = require('co'); +co(gen); +``` + +上面代码中,Generator函数只要传入co函数,就会自动执行。 + +co函数返回一个Promise对象,因此可以用then方法添加回调函数。 + +```javascript +co(gen).then(function (){ + console.log('Generator 函数执行完成'); +}) +``` + +上面代码中,等到Generator函数执行结束,就会输出一行提示。 + +### co模块的原理 + +为什么co可以自动执行Generator函数? + +前面说过,Generator就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。 + +两种方法可以做到这一点。 + +(1)回调函数。将异步操作包装成Thunk函数,在回调函数里面交回执行权。 + +(2)Promise 对象。将异步操作包装成Promise对象,用then方法交回执行权。 + +co模块其实就是将两种自动执行器(Thunk函数和Promise对象),包装成一个模块。使用co的前提条件是,Generator函数的yield命令后面,只能是Thunk函数或Promise对象。 + +上一节已经介绍了基于Thunk函数的自动执行器。下面来看,基于Promise对象的自动执行器。这是理解co模块必须的。 + +### 基于Promise对象的自动执行 + +还是沿用上面的例子。首先,把fs模块的readFile方法包装成一个Promise对象。 + +```javascript +var fs = require('fs'); + +var readFile = function (fileName){ + return new Promise(function (resolve, reject){ + fs.readFile(fileName, function(error, data){ + if (error) reject(error); + resolve(data); + }); + }); +}; + +var gen = function* (){ + var f1 = yield readFile('/etc/fstab'); + var f2 = yield readFile('/etc/shells'); + console.log(f1.toString()); + console.log(f2.toString()); +}; +``` + +然后,手动执行上面的Generator函数。 + +```javascript +var g = gen(); + +g.next().value.then(function(data){ + g.next(data).value.then(function(data){ + g.next(data); + }); +}) +``` + +手动执行其实就是用then方法,层层添加回调函数。理解了这一点,就可以写出一个自动执行器。 + +```javascript +function run(gen){ + var g = gen(); + + function next(data){ + var result = g.next(data); + if (result.done) return result.value; + result.value.then(function(data){ + next(data); + }); + } + + next(); +} + +run(gen); +``` + +上面代码中,只要Generator函数还没执行到最后一步,next函数就调用自身,以此实现自动执行。 + +### co模块的源码 + +co就是上面那个自动执行器的扩展,它的源码只有几十行,非常简单。 + +首先,co函数接受Generator函数作为参数,返回一个 Promise 对象。 + +```javascript +function co(gen) { + var ctx = this; + + return new Promise(function(resolve, reject) { + }); +} +``` + +在返回的Promise对象里面,co先检查参数gen是否为Generator函数。如果是,就执行该函数,得到一个内部指针对象;如果不是就返回,并将Promise对象的状态改为resolved。 + +```javascript +function co(gen) { + var ctx = this; + + return new Promise(function(resolve, reject) { + if (typeof gen === 'function') gen = gen.call(ctx); + if (!gen || typeof gen.next !== 'function') return resolve(gen); + }); +} +``` + +接着,co将Generator函数的内部指针对象的next方法,包装成onFulefilled函数。这主要是为了能够捕捉抛出的错误。 + +```javascript +function co(gen) { + var ctx = this; + + return new Promise(function(resolve, reject) { + if (typeof gen === 'function') gen = gen.call(ctx); + if (!gen || typeof gen.next !== 'function') return resolve(gen); + + onFulfilled(); + function onFulfilled(res) { + var ret; + try { + ret = gen.next(res); + } catch (e) { + return reject(e); + } + next(ret); + } + }); +} +``` + +最后,就是关键的next函数,它会反复调用自身。 + +```javascript +function next(ret) { + if (ret.done) return resolve(ret.value); + var value = toPromise.call(ctx, ret.value); + if (value && isPromise(value)) return value.then(onFulfilled, onRejected); + return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + + 'but the following object was passed: "' + String(ret.value) + '"')); +} +``` + +上面代码中,next 函数的内部代码,一共只有四行命令。 + +- 第一行,检查当前是否为 Generator 函数的最后一步,如果是就返回。 + +- 第二行,确保每一步的返回值,是 Promise 对象。 + +- 第三行,使用 then 方法,为返回值加上回调函数,然后通过 onFulfilled 函数再次调用 next 函数。 + +- 第四行,在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为 rejected,从而终止执行。 + +### 处理并发的异步操作 + +co支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。 + +这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。 + +```javascript +// 数组的写法 +co(function* () { + var res = yield [ + Promise.resolve(1), + Promise.resolve(2) + ]; + console.log(res); +}).catch(onerror); + +// 对象的写法 +co(function* () { + var res = yield { + 1: Promise.resolve(1), + 2: Promise.resolve(2), + }; + console.log(res); +}).catch(onerror); +``` + +下面是另一个例子。 ```javascript co(function* () { @@ -483,3 +691,308 @@ function* somethingAsync(x) { ``` 上面的代码允许并发三个somethingAsync异步操作,等到它们全部完成,才会进行下一步。 + +## async函数 + +### 含义 + +async 函数是什么?一句话,async函数就是Generator函数的语法糖。 + +前文有一个Generator函数,依次读取两个文件。 + +```javascript +var fs = require('fs'); + +var readFile = function (fileName){ + return new Promise(function (resolve, reject){ + fs.readFile(fileName, function(error, data){ + if (error) reject(error); + resolve(data); + }); + }); +}; + +var gen = function* (){ + var f1 = yield readFile('/etc/fstab'); + var f2 = yield readFile('/etc/shells'); + console.log(f1.toString()); + console.log(f2.toString()); +}; +``` + +写成 async 函数,就是下面这样。 + +```javascript +var asyncReadFile = async function (){ + var f1 = await readFile('/etc/fstab'); + var f2 = await readFile('/etc/shells'); + console.log(f1.toString()); + console.log(f2.toString()); +}; +``` + +一比较就会发现,async函数就是将Generator函数的星号(*)替换成async,将yield替换成await,仅此而已。 + +async 函数对 Generator 函数的改进,体现在以下三点。 + +(1)内置执行器。Generator函数的执行必须靠执行器,所以才有了co模块,而async 函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。 + +```javascript +var result = asyncReadFile(); +``` + +(2)更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。 + +(3)更广的适用性。 co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以跟Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。 + +### async函数的实现 + +async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。 + +```javascript +async function fn(args){ + // ... +} + +// 等同于 + +function fn(args){ + return spawn(function*() { + // ... + }); +} +``` + +所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。 + +下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。 + +```javascript +function spawn(genF) { + return new Promise(function(resolve, reject) { + var gen = genF(); + function step(nextF) { + try { + var next = nextF(); + } catch(e) { + return reject(e); + } + if(next.done) { + return resolve(next.value); + } + 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 函数是非常新的语法功能,新到都不属于 ES6,而是属于 ES7。目前,它仍处于提案阶段,但是转码器 Babel 和 regenerator 都已经支持,转码后就能使用。 + +### async 函数的用法 + +同Generator函数一样,async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。 + +下面是一个例子。 + +```javascript +async function getStockPriceByName(name) { + var symbol = await getStockSymbol(name); + var stockPrice = await getStockPrice(symbol); + return stockPrice; +} + +getStockPriceByName('goog').then(function (result){ + console.log(result); +}); +``` + +上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。 + +下面的例子,指定多少毫秒后输出一个值。 + +```javascript +function timeout(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +async function asyncPrint(value, ms) { + await timeout(ms); + console.log(value) +} + +asyncPrint('hello world', 50); +``` + +上面代码指定50毫秒以后,输出"hello world"。 + +### 注意点 + +await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。 + +```javascript +async function myFunction() { + try { + await somethingThatReturnsAPromise(); + } catch (err) { + console.log(err); + } +} + +// 另一种写法 + +async function myFunction() { + await somethingThatReturnsAPromise().catch(function (err){ + console.log(err); + }; +} +``` + +await命令只能用在async函数之中,如果用在普通函数,就会报错。 + +```javascript +async function dbFuc(db) { + let docs = [{}, {}, {}]; + + // 报错 + docs.forEach(function (doc) { + await db.post(doc); + }); +} +``` + +上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。 + +```javascript +async function dbFuc(db) { + let docs = [{}, {}, {}]; + + // 可能得到错误结果 + docs.forEach(async function (doc) { + await db.post(doc); + }); +} +``` + +上面代码可能不会正常工作,原因是这时三个`db.post`操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。 + +```javascript +async function dbFuc(db) { + let docs = [{}, {}, {}]; + + for (let doc of docs) { + await db.post(doc); + } +} +``` + +如果确实希望多个请求并发执行,可以使用 Promise.all 方法。 + +```javascript +async function dbFuc(db) { + let docs = [{}, {}, {}]; + let promises = docs.map((doc) => db.post(doc)); + + let results = await Promise.all(promises); + console.log(results); +} + +// 或者使用下面的写法 + +async function dbFuc(db) { + let docs = [{}, {}, {}]; + let promises = docs.map((doc) => db.post(doc)); + + let results = []; + for (let promise of promises) { + results.push(await promise); + } + console.log(results); +} +``` + +ES6将await增加为保留字。使用这个词作为标识符,在ES5是合法的,在ES6将抛出SyntaxError。 + +### 与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。 + +最后是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写法,自动执行器需要用户自己提供。 + diff --git a/docs/intro.md b/docs/intro.md index addd1006f..8860d815f 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -4,7 +4,7 @@ ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准,已经 ES6的目标,是使得JavaScript语言可以用来编写大型的复杂的应用程序,成为企业级开发语言。 -标准的制定者计划,以后每年发布一次标准,使用年份作为标准的版本。因为当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015。 +标准的制定者有计划,以后每年发布一次标准,使用年份作为标准的版本。因为当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015。 ## ECMAScript和JavaScript的关系 @@ -330,3 +330,5 @@ ES7可能包括的功能有: (4)**Traits**:它将是“类”功能(class)的一个替代。通过它,不同的对象可以分享同样的特性。 其他可能包括的功能还有:更精确的数值计算、改善的内存回收、增强的跨站点安全、类型化的更贴近硬件的低级别操作、国际化支持(Internationalization Support)、更多的数据结构等等。 + +本书对于那些明确的、或者很有希望列入ES7的功能,尤其是那些Babel已经支持的功能,都将予以介绍。 diff --git a/docs/promise.md b/docs/promise.md index 4f70c0545..bf46f792f 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -428,7 +428,6 @@ p.then(null, function (s){ 使用Generator函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。 ```javascript - function getFoo () { return new Promise(function (resolve, reject){ resolve('foo'); @@ -446,10 +445,10 @@ var g = function* () { function run (generator) { var it = generator(); - + function go(result) { if (result.done) return result.value; - + return result.value.then(function (value) { return go(it.next(value)); }, function (error) { @@ -461,345 +460,13 @@ function run (generator) { } run(g); - ``` 上面代码的Generator函数g之中,有一个异步操作getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。 ## async函数 -### 概述 - async函数与Promise、Generator函数一样,是用来取代回调函数、解决异步操作的一种方法。它本质上是Generator函数的语法糖。async函数并不属于ES6,而是被列入了ES7,但是traceur、Babel.js、regenerator等转码器已经支持这个功能,转码后立刻就能使用。 -下面是一个Generator函数,依次读取两个文件。 - -```javascript -var fs = require('fs'); - -var readFile = function (fileName){ - return new Promise(function (resolve, reject){ - fs.readFile(fileName, function(error, data){ - if (error) reject(error); - resolve(data); - }); - }); -}; - -var gen = function* (){ - var f1 = yield readFile('/etc/fstab'); - var f2 = yield readFile('/etc/shells'); - console.log(f1.toString()); - console.log(f2.toString()); -}; -``` - -上面代码中,readFile函数是`fs.readFile`的Promise版本。 - -写成async函数,就是下面这样。 - -```javascript -var asyncReadFile = async function (){ - var f1 = await readFile('/etc/fstab'); - var f2 = await readFile('/etc/shells'); - console.log(f1.toString()); - console.log(f2.toString()); -}; -``` - -一比较就会发现,async函数就是将Generator函数的星号(*)替换成async,将yield替换成await,仅此而已。 - -async函数对Generator函数的改进,体现在以下三点。 - -(1)内置执行器。Generator函数的执行必须靠执行器,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。 - -```javascript -var result = asyncReadFile(); -``` - -(2)更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。 - -(3)更广的适用性。co函数库约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以跟Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。 - -### 实现 - -async函数的实现,就是将Generator函数和自动执行器,包装在一个函数里。 - -```javascript -async function fn(args){ - // ... -} - -// 等同于 - -function fn(args){ - return spawn(function*() { - // ... - }); -} -``` - -所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。 - -下面给出spawn函数的实现,基本就是前文自动执行器的翻版。 - -```javascript -function spawn(genF) { - return new Promise(function(resolve, reject) { - var gen = genF(); - function step(nextF) { - try { - var next = nextF(); - } catch(e) { - return reject(e); - } - if(next.done) { - return resolve(next.value); - } - 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); }); - }); -} -``` - -### 用法 - -同Generator函数一样,async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。 - -下面是一个例子。 - -```javascript - -async function getStockPriceByName(name) { - var symbol = await getStockSymbol(name); - var stockPrice = await getStockPrice(symbol); - return stockPrice; -} - -getStockPriceByName('goog').then(function (result){ - console.log(result); -}); - -``` - -上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。 - -上面的例子用Generator函数表达,就是下面这样。 - -```javascript -function getStockPriceByName(name) { - return spawn(function*(name) { - var symbol = yield getStockSymbol(name); - var stockPrice = yield getStockPrice(symbol); - return stockPrice; - }); -} -``` - -上面的例子中,spawn函数是一个自动执行器,由JavaScript引擎内置。它的参数是一个Generator函数。async...await结构本质上,是在语言层面提供的异步任务的自动执行器。 - -下面是一个更一般性的例子,指定多少毫秒后输出一个值。 - -```javascript - -function timeout(ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} - -async function asyncPrint(value, ms) { - await timeout(ms); - console.log(value) -} - -asyncPrint('hello world', 50); - -``` - -上面代码指定50毫秒以后,输出“hello world”。 - -### 注意点 - -await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。 - -```javascript - -async function myFunction() { - try { - await somethingThatReturnsAPromise(); - } catch (err) { - console.log(err); - } -} - -// 另一种写法 - -async function myFunction() { - await somethingThatReturnsAPromise().catch(function (err){ - console.log(err); - }; -} - -``` - -await命令只能用在async函数之中,如果用在普通函数,就会报错。 - -```javascript - -async function dbFuc(db) { - let docs = [{}, {}, {}]; - - // 报错 - docs.forEach(function (doc) { - await db.post(doc); - }); -} - -``` - -上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。 - -```javascript - -async function dbFuc(db) { - let docs = [{}, {}, {}]; - - // 可能得到错误结果 - docs.forEach(async function (doc) { - await db.post(doc); - }); -} - -``` - -上面代码可能不会正常工作,原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。 - -```javascript - -async function dbFuc(db) { - let docs = [{}, {}, {}]; - - for (let doc of docs) { - await db.post(doc); - } -} - -``` - -如果确实希望多个请求并发执行,可以使用Promise.all方法。 - -```javascript - -async function dbFuc(db) { - let docs = [{}, {}, {}]; - let promises = docs.map((doc) => db.post(doc)); - - let results = await Promise.all(promises); - console.log(results); -} - -// 或者使用下面的写法 - -async function dbFuc(db) { - let docs = [{}, {}, {}]; - let promises = docs.map((doc) => db.post(doc)); - - let results = []; - for (let promise of promises) { - results.push(await promise); - } - console.log(results); -} - -``` - -ES6将await增加为保留字。使用这个词作为标识符,在ES5是合法的,在ES6将抛出SyntaxError。 - -### 与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。 - -最后是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函数的详细介绍,请看《异步操作》一章。 -可以看到Async函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将Generator写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用Generator写法,自动执行器需要用户自己提供。 From 6b4181d329c1c2a26526e14e0ba2390127321cc5 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 17 Jul 2015 18:59:56 +0800 Subject: [PATCH 0007/1267] edit let --- docs/let.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/let.md b/docs/let.md index 78184f1a6..26dfb81d0 100644 --- a/docs/let.md +++ b/docs/let.md @@ -156,13 +156,13 @@ let不允许在相同作用域内,重复声明同一个变量。 ```javascript // 报错 -{ +function () { let a = 10; var a = 1; } // 报错 -{ +function () { let a = 10; let a = 1; } From ef0695403f4aa75c2f8a985ce4de9c0b5c8484b3 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Mon, 20 Jul 2015 16:24:50 +0800 Subject: [PATCH 0008/1267] =?UTF-8?q?=E5=8A=A0=E5=85=A5mixin=E5=92=8Ctrait?= =?UTF-8?q?=E7=9A=84=E4=BB=8B=E7=BB=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 178 ++++++++++++++++++++++++++++++++++++++++++++-- docs/function.md | 32 ++++----- docs/intro.md | 40 ++++++++++- docs/reference.md | 12 ++-- 4 files changed, 234 insertions(+), 28 deletions(-) diff --git a/docs/class.md b/docs/class.md index e47a64c72..77091f81f 100644 --- a/docs/class.md +++ b/docs/class.md @@ -983,9 +983,9 @@ class Person { ### core-decorators.js -[core-decorators.js](https://github.com/jayphelps/core-decorators.js)提供了几个常见的修饰器。 +[core-decorators.js](https://github.com/jayphelps/core-decorators.js)是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器。 -(1)@autobind +**(1)@autobind** autobind修饰器使得方法中的this对象,绑定原始对象。 @@ -1006,7 +1006,7 @@ getPerson() === person; // true ``` -(2)@readonly +**(2)@readonly** readonly修饰器是的属性或方法不可写。 @@ -1023,7 +1023,7 @@ dinner.entree = 'salmon'; // Cannot assign to read only property 'entree' of [object Object] ``` -(3)@override +**(3)@override** override修饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。 @@ -1051,7 +1051,7 @@ class Child extends Parent { } ``` -(4)@deprecate (别名@deprecated) +**(4)@deprecate (别名@deprecated)** deprecate或deprecated修饰器在控制台显示一条警告,表示该方法将废除。 @@ -1084,7 +1084,7 @@ person.facepalmHarder(); // ``` -(5)@suppressWarnings +**(5)@suppressWarnings** suppressWarnings修饰器抑制decorated修饰器导致的`console.warn()`调用。但是,异步代码出发的调用除外。 @@ -1107,6 +1107,172 @@ person.facepalmWithoutWarning(); // no warning is logged ``` +### Mixin + +在修饰器的基础上,可以实现Mixin模式。所谓Mixin模式,就是对象继承的一种替代方案,中文译为“混入”(mix in),意为在一个对象之中混入另外一个对象的方法。 + +请看下面的例子。 + +```javascript +const Foo = { + foo() { console.log('foo') } +}; + +class MyClass {} + +Object.assign(MyClass.prototype, Foo); + +let obj = new MyClass(); +obj.foo() // 'foo' +``` + +上面代码之中,对象Foo有一个foo方法,通过`Object.assign`方法,可以将foo方法“混入”MyClass类,导致MyClass的实例obj对象都具有foo方法。这就是“混入”模式的一个简单实现。 + +下面,我们部署一个通用脚本`mixins.js`,将mixin写成一个修饰器。 + +```javascript +export function mixins(...list) { + return function (target) { + Object.assign(target.prototype, ...list); + }; +} +``` + +然后,就可以使用上面这个修饰器,为类“混入”各种方法。 + +```javascript +import { mixins } from './mixins' + +const Foo = { + foo() { console.log('foo') } +}; + +@mixins(Foo) +class MyClass {} + +let obj = new MyClass(); + +obj.foo() // "foo" +``` + +通过mixins这个修饰器,实现了在MyClass类上面“混入”Foo对象的foo方法。 + +### Trait + +Trait也是一种修饰器,功能与Mixin类型,但是提供更多功能,比如防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等等。 + +下面采用[traits-decorator](https://github.com/CocktailJS/traits-decorator)这个第三方模块作为例子。这个模块提供的traits修饰器,不仅可以接受对象,还可以接受ES6类作为参数。 + +```javascript +import {traits } from 'traits-decorator' + +class TFoo { + foo() { console.log('foo') } +} + +const TBar = { + bar() { console.log('bar') } +} + +@traits(TFoo, TBar) +class MyClass { } + +let obj = new MyClass() +obj.foo() // foo +obj.bar() // bar +``` + +上面代码中,通过traits修饰器,在MyClass类上面“混入”了TFoo类的foo方法和TBar对象的bar方法。 + +Trait不允许“混入”同名方法。 + +```javascript +import {traits } from 'traits-decorator' + +class TFoo { + foo() { console.log('foo') } +} + +const TBar = { + bar() { console.log('bar') }, + foo() { console.log('foo') } +} + +@traits(TFoo, TBar) +class MyClass { } +// 报错 +// throw new Error('Method named: ' + methodName + ' is defined twice.'); +// ^ +// Error: Method named: foo is defined twice. +``` + +上面代码中,TFoo和TBar都有foo方法,结果traits修饰器报错。 + +一种解决方法是排除TBar的foo方法。 + +```javascript +import { traits, excludes } from 'traits-decorator' + +class TFoo { + foo() { console.log('foo') } +} + +const TBar = { + bar() { console.log('bar') }, + foo() { console.log('foo') } +} + +@traits(TFoo, TBar::excludes('foo')) +class MyClass { } + +let obj = new MyClass() +obj.foo() // foo +obj.bar() // bar +``` + +上面代码使用绑定运算符(::)在TBar上排除foo方法,混入时就不会报错了。 + +另一种方法是为TBar的foo方法起一个别名。 + +```javascript +import { traits, alias } from 'traits-decorator' + +class TFoo { + foo() { console.log('foo') } +} + +const TBar = { + bar() { console.log('bar') }, + foo() { console.log('foo') } +} + +@traits(TFoo, TBar::alias({foo: 'aliasFoo'})) +class MyClass { } + +let obj = new MyClass() +obj.foo() // foo +obj.aliasFoo() // foo +obj.bar() // bar +``` + +上面代码为TBar的foo方法起了别名aliasFoo,于是MyClass也可以混入TBar的foo方法了。 + +alias和excludes方法,可以结合起来使用。 + +```javascript +@traits(TExample::excludes('foo','bar')::alias({baz:'exampleBaz'})) +class MyClass {} +``` + +上面代码排除了TExample的foo方法和bar方法,为baz方法起了别名exampleBaz。 + +as方法则为上面的代码提供了另一种写法。 + +```javascript +@traits(TExample::as({excludes:['foo', 'bar'], alias: {baz: 'exampleBaz'}})) +class MyClass {} +``` + ### Babel转码器的支持 目前,Babel转码器已经支持Decorator,命令行的用法如下。 diff --git a/docs/function.md b/docs/function.md index 6674c8961..0c35f8c7d 100644 --- a/docs/function.md +++ b/docs/function.md @@ -460,7 +460,7 @@ var f = v => v; ```javascript var f = function(v) { - return v; + return v; }; ``` @@ -474,7 +474,7 @@ var f = function (){ return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { - return num1 + num2; + return num1 + num2; }; ``` @@ -527,7 +527,7 @@ const square = n => n * n; ```javascript // 正常函数写法 var result = values.sort(function(a, b) { - return a - b; + return a - b; }); // 箭头函数写法 @@ -627,6 +627,19 @@ mult2(plus1(5)) // 12 ``` +箭头函数还有一个功能,就是可以很方便地改写λ演算。 + +```javascript +// λ演算的写法 +fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v))) + +// ES6的写法 +var fix = f => (x => f(v => x(x)(v))) + (x => f(v => x(x)(v))); +``` + +上面两种写法,几乎是一一对应的。由于λ演算对于计算机科学非常重要,这使得我们可以用ES6作为替代工具,探索计算机科学。 + ## 函数绑定 箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以ES7提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。虽然该语法还是ES7的一个提案,但是Babel转码器已经支持。 @@ -647,19 +660,6 @@ i// 等同于 bar.apply(foo, arguments); ``` -箭头函数还有一个功能,就是可以很方便地改写λ演算。 - -```javascript -// λ演算的写法 -fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v))) - -// ES6的写法 -var fix = f => (x => f(v => x(x)(v))) - (x => f(v => x(x)(v))); -``` - -上面两种写法,几乎是一一对应的。由于λ演算对于计算机科学非常重要,这使得我们可以用ES6作为替代工具,探索计算机科学。 - ## 尾调用优化 ### 什么是尾调用? diff --git a/docs/intro.md b/docs/intro.md index 8860d815f..f5f90d60d 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -120,7 +120,21 @@ $ es-checker ## Babel转码器 -[Babel](https://babeljs.io/)是一个广泛使用的ES6转码器,可以ES6代码转为ES5代码,从而在浏览器或其他环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。它的安装命令如下。 +[Babel](https://babeljs.io/)是一个广泛使用的ES6转码器,可以ES6代码转为ES5代码,从而在浏览器或其他环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。 + +```javascript +// 转码前 +input.map(item => item + 1); + +// 转码后 +input.map(function (item) { + return item + 1; +}); +``` + +上面的原始代码用了箭头函数,这个特性还没有得到广泛支持,Babel将其转为普通函数,就能在现有的JavaScript环境执行了。 + +它的安装命令如下。 ```bash $ npm install --global babel @@ -154,10 +168,26 @@ console.log([1, 2, 3].map(function (x) { })); ``` -`-o` 参数将转换后的代码,从标准输出导入文件。 +`-o`参数将转换后的代码,从标准输出导入文件。 ```bash $ babel es6.js -o es5.js +# 或者 +$ babel es6.js --out-file es5.js +``` + +`-d`参数用于转换整个目录。 + +```bash +$ babel -d build-dir source-dir +``` + +注意,`-d`参数后面跟的是输出目录。 + +如果希望生成source map文件,则要加上`-s`参数。 + +```bash +$ babel -d build-dir source-dir -s ``` Babel也可以用于浏览器。 @@ -171,6 +201,12 @@ Babel也可以用于浏览器。 上面代码中,`browser.js`是Babel提供的转换器脚本,可以在浏览器运行。用户的ES6脚本放在script标签之中,但是要注明`type="text/babel"`。 +Babel配合Browserify一起使用,可以生成浏览器能够直接加载的脚本。 + +```bash +$ browserify script.js -t babelify --outfile bundle.js +``` + ## Traceur转码器 Google公司的[Traceur](https://github.com/google/traceur-compiler)转码器,也可以将ES6代码转为ES5代码。 diff --git a/docs/reference.md b/docs/reference.md index a630f7705..0ce5bc42f 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -113,13 +113,17 @@ - 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规格的详细比较 -- 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语法的详细介绍和设计思想分析 -- Maximiliano Fierro, [Declarative vs Imperative](http://elmasse.github.io/js/decorators-bindings-es7.html): Decorators介绍 +- Maximiliano Fierro, [Declarative vs Imperative](http://elmasse.github.io/js/decorators-bindings-es7.html): Decorators和Mixin介绍 - Addy Osmani, [Exploring ES2016 Decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841): Decorator的深入介绍 +- Maximiliano Fierro, [Traits with ES7 Decorators](http://cocktailjs.github.io/blog/traits-with-es7-decorators.html): Trait的用法介绍 + +## 模块 + +- 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 449216a9c1cadc30de72583edf9a30ed634ebb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=97=B2=E8=80=98=E2=84=A2?= Date: Tue, 21 Jul 2015 20:03:06 +0800 Subject: [PATCH 0009/1267] =?UTF-8?q?fix(class):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BA=86=E4=BB=A3=E7=A0=81=E4=B8=AD=20class=20=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E5=AD=97=E7=9A=84=E6=8B=BC=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/class.md b/docs/class.md index 77091f81f..913b13919 100644 --- a/docs/class.md +++ b/docs/class.md @@ -44,7 +44,7 @@ Point类除了构造方法,还定义了一个toString方法。注意,定义 ES6的类,完全可以看作构造函数的另一种写法。 ```javascript -Class Point{ +class Point{ // ... } @@ -56,7 +56,7 @@ typeof Point // "function" 构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,除了constructor方法以外,类的方法都定义在类的prototype属性上面。 ```javascript -Class Point { +class Point { constructor(){ // ... } @@ -81,7 +81,7 @@ Point.prototype = { 由于类的方法(除constructor以外)都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。`Object.assign`方法可以很方便地一次向类添加多个方法。 ```javascript -Class Point { +class Point { constructor(){ // ... } @@ -1288,5 +1288,3 @@ babel.transfrom("code", {optional: ["es7.decorators"]}) ``` Babel的官方网站提供一个[在线转码器](https://babeljs.io/repl/),只要勾选Experimental,就能支持Decorator的在线转码。 - - From e80ed6518091df4c50be5049843a25818e0ca2b2 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Wed, 22 Jul 2015 09:20:13 +0800 Subject: [PATCH 0010/1267] edit class --- docs/class.md | 98 +++++++++++++++--------------- docs/object.md | 160 +++++++++++++++++++++++++++---------------------- 2 files changed, 140 insertions(+), 118 deletions(-) diff --git a/docs/class.md b/docs/class.md index 913b13919..740f06772 100644 --- a/docs/class.md +++ b/docs/class.md @@ -325,53 +325,7 @@ class Foo {} 如果存在Class的提升,上面代码将报错,因为let命令也是不提升的。 -**(7)存取器** - -Class支持set和get方法,设置赋值器和取值器,拦截属性的存取行为。 - -```javascript -class Jedi { - constructor(options = {}) { - // ... - } - - set(key, val) { - this[key] = val; - } - - get(key) { - return this[key]; - } -} -``` - -上面代码中,Jedi实例所有属性的存取,都会通过存取器。 - -下面的例子是针对某个属性,设置存取器。 - -```javascript -class CustomHTMLElement { - constructor(element) { - this.element = element; - } - - get html() { - return this.element.innerHTML; - } - - set html(value) { - this.element.innerHTML = value; - } -} - -var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html"); -"get" in descriptor // true -"set" in descriptor // true -``` - -上面代码中,只有html属性的存取,会通过存取器,而存取器是定义在html属性的描述对象上面,这与ES5完全一致。 - -**(8)严格模式** +**(7)严格模式** 类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。 @@ -629,10 +583,13 @@ throw new MyError('Something happened!'); ## class的取值函数(getter)和存值函数(setter) -与ES5一样,在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数。 +与ES5一样,在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。 ```javascript class MyClass { + constructor() { + // ... + } get prop() { return 'getter'; } @@ -652,6 +609,51 @@ inst.prop 上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。 +存值函数和取值函数是设置在属性的descriptor对象上的。 + +```javascript +class CustomHTMLElement { + constructor(element) { + this.element = element; + } + + get html() { + return this.element.innerHTML; + } + + set html(value) { + this.element.innerHTML = value; + } +} + +var descriptor = Object.getOwnPropertyDescriptor( + CustomHTMLElement.prototype, "html"); +"get" in descriptor // true +"set" in descriptor // true +``` + +上面代码中,存值函数和取值函数是定义在html属性的描述对象上面,这与ES5完全一致。 + +下面的例子针对所有属性,设置存值函数和取值函数。 + +```javascript +class Jedi { + constructor(options = {}) { + // ... + } + + set(key, val) { + this[key] = val; + } + + get(key) { + return this[key]; + } +} +``` + +上面代码中,Jedi实例所有属性的存取,都会通过存值函数和取值函数。 + ## Class的Generator方法 如果某个方法之前加上星号(*),就表示该方法是一个Generator函数。 diff --git a/docs/object.md b/docs/object.md index 6a9f5e9e9..6a66fa386 100644 --- a/docs/object.md +++ b/docs/object.md @@ -19,7 +19,6 @@ function f( x, y ) { 上面是属性简写的例子,方法也可以简写。 ```javascript - var o = { method() { return "Hello!"; @@ -33,13 +32,11 @@ var o = { return "Hello!"; } }; - ``` 下面是一个更实际的例子。 ```javascript - var Person = { name: '张三', @@ -51,13 +48,11 @@ var Person = { hello() { console.log('我的名字是', this.name); } }; - ``` 这种写法用于函数的返回值,将会非常方便。 ```javascript - function getPoint() { var x = 1; var y = 10; @@ -67,7 +62,6 @@ function getPoint() { getPoint() // {x:1, y:10} - ``` ## 属性名表达式 @@ -75,13 +69,11 @@ getPoint() JavaScript语言定义对象的属性,有两种方法。 ```javascript - // 方法一 obj.foo = true; // 方法二 obj['a'+'bc'] = 123; - ``` 上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。 @@ -89,31 +81,26 @@ 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 = { @@ -124,13 +111,11 @@ var a = { a["first word"] // "hello" a[lastWord] // "world" a["last word"] // "world" - ``` 表达式还可以用于定义方法名。 ```javascript - let obj = { ['h'+'ello']() { return 'hi'; @@ -138,7 +123,6 @@ let obj = { }; console.log(obj.hello()); // hi - ``` ## 方法的name属性 @@ -163,7 +147,7 @@ person.firstName.name // "get firstName" ```javascript var doSomething = function() { - // ... + // ... }; console.log(doSomething.bind().name); // "bound doSomething" @@ -187,19 +171,16 @@ doSomething.bind().name // "bound doSomething" Object.is()用来比较两个值是否严格相等。它与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身。 ```javascript - +0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true - ``` ES5可以通过下面的代码,部署Object.is()。 ```javascript - Object.defineProperty(Object, 'is', { value: function(x, y) { if (x === y) { @@ -213,7 +194,6 @@ Object.defineProperty(Object, 'is', { enumerable: false, writable: true }); - ``` ## Object.assign() @@ -233,7 +213,6 @@ target // {a:1, b:2, c:3} 注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。 ```javascript - var target = { a: 1, b: 1 }; var source1 = { b: 2, c: 2 }; @@ -241,7 +220,6 @@ var source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3} - ``` assign方法有很多用处。 @@ -249,19 +227,16 @@ assign方法有很多用处。 **(1)为对象添加属性** ```javascript - class Point { constructor(x, y) { Object.assign(this, {x, y}); } } - ``` 上面方法通过assign方法,将x属性和y属性添加到Point类的对象实例。 **(2)为对象添加方法** - ```javascript Object.assign(SomeClass.prototype, { @@ -280,7 +255,6 @@ SomeClass.prototype.someMethod = function (arg1, arg2) { SomeClass.prototype.anotherMethod = function () { ··· }; - ``` 上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用assign方法添加到SomeClass.prototype之中。 @@ -288,11 +262,9 @@ SomeClass.prototype.anotherMethod = function () { **(3)克隆对象** ```javascript - function clone(origin) { return Object.assign({}, origin); } - ``` 上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。 @@ -300,12 +272,10 @@ function clone(origin) { 不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。 ```javascript - function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin); } - ``` **(4)合并多个对象** @@ -327,7 +297,6 @@ const merge = **(5)为属性指定默认值** ```javascript - const DEFAULTS = { logLevel: 0, outputFormat: 'html' @@ -336,7 +305,6 @@ const DEFAULTS = { function processContent(options) { let options = Object.assign({}, DEFAULTS, options); } - ``` 上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。 @@ -348,7 +316,6 @@ function processContent(options) { __proto__属性,用来读取或设置当前对象的prototype对象。该属性一度被正式写入ES6草案,但后来又被移除。目前,所有浏览器(包括IE11)都部署了这个属性。 ```javascript - // es6的写法 var obj = { @@ -360,7 +327,6 @@ var obj = { var obj = Object.create(someOtherObj); obj.method = function() { ... } - ``` 有了这个属性,实际上已经不需要通过Object.create()来生成新对象了。 @@ -370,24 +336,20 @@ obj.method = function() { ... } Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。它是ES6正式推荐的设置原型对象的方法。 ```javascript - // 格式 Object.setPrototypeOf(object, prototype) // 用法 var o = Object.setPrototypeOf({}, null); - ``` 该方法等同于下面的函数。 ```javascript - function (obj, proto) { obj.__proto__ = proto; return obj; } - ``` **(3)Object.getPrototypeOf()** @@ -395,9 +357,7 @@ function (obj, proto) { 该方法与setPrototypeOf方法配套,用于读取一个对象的prototype对象。 ```javascript - Object.getPrototypeOf(obj) - ``` ## Symbol @@ -691,23 +651,23 @@ iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo') 除了定义自己使用的Symbol值以外,ES6还提供一些内置的Symbol值,指向语言内部使用的方法。 -(1)Symbol.hasInstance +**(1)Symbol.hasInstance** 对象的Symbol.hasInstance属性,指向一个内部方法。该对象使用instanceof运算符时,会调用这个方法,判断该对象是否为某个构造函数的实例。比如,`foo instanceof Foo`在语言内部,实际调用的是`Foo[Symbol.hasInstance](foo)`。 -(2)Symbol.isConcatSpreadable +**(2)Symbol.isConcatSpreadable** 对象的Symbol.isConcatSpreadable属性,指向一个方法。该对象使用Array.prototype.concat()时,会调用这个方法,返回一个布尔值,表示该对象是否可以扩展成数组。 -(3)Symbol.isRegExp +**(3)Symbol.isRegExp** 对象的Symbol.isRegExp属性,指向一个方法。该对象被用作正则表达式时,会调用这个方法,返回一个布尔值,表示该对象是否为一个正则对象。 -(4)Symbol.match +**(4)Symbol.match** 对象的Symbol.match属性,指向一个函数。当执行`str.match(myObject)`时,如果该属性存在,会调用它,返回该方法的返回值。 -(5)Symbol.iterator +**(5)Symbol.iterator** 对象的Symbol.iterator属性,指向该对象的默认遍历器方法,即该对象进行for...of循环时,会调用这个方法,返回该对象的默认遍历器,详细介绍参见《Iterator和for...of循环》一章。 @@ -734,11 +694,11 @@ for(let value of myCollection) { // 2 ``` -(6)Symbol.toPrimitive +**(6)Symbol.toPrimitive** 对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 -(7)Symbol.toStringTag +**(7)Symbol.toStringTag** 对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用`Object.prototype.toString`方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制`[object Object]`或`[object Array]`中object后面的那个字符串。 @@ -752,7 +712,7 @@ var x = new Collection(); Object.prototype.toString.call(x) // "[object xxx]" ``` -(8)Symbol.unscopables +**(8)Symbol.unscopables** 对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,那些属性会被with环境排除。 @@ -806,7 +766,33 @@ with (MyClass.prototype) { Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。 -Proxy可以理解成在目标对象之前,架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作。 +Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。 + +```javascript +var obj = new Proxy({}, { + get: function (target, key, receiver) { + console.log(`getting ${key}!`); + return Reflect.get(target, key, receiver); + }, + set: function (target, key, value, receiver) { + console.log(`setting ${key}!`); + return Reflect.set(target, key, value, receiver); + } +}); +``` + +上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。 + +```javascript +obj.count = 1 +// setting count! +++obj.count +// getting count! +// setting count! +// 2 +``` + +上面代码说明,Proxy实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。 ES6原生提供Proxy构造函数,用来生成Proxy实例。 @@ -814,9 +800,9 @@ ES6原生提供Proxy构造函数,用来生成Proxy实例。 var proxy = new Proxy(target, handler) ``` -Proxy对象的使用方法,都是上面这种形式。`new Proxy()`表示生成一个Proxy实例,它的target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。 +Proxy对象的所用用法,都是上面这种形式,不同的只是handler参数的写法。其中,`new Proxy()`表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。 -下面是一个使用实例。 +下面是另一个拦截读取属性行为的例子。 ```javascript var proxy = new Proxy({}, { @@ -830,14 +816,14 @@ proxy.name // 35 proxy.title // 35 ``` -上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个设置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,设置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。 +上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。 注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。 一个技巧是将Proxy对象,设置到`object.proxy`属性,从而可以在object对象上调用。 ```javascript -var object = { proxy: new Proxy(target, handler) }. +var object = { proxy: new Proxy(target, handler) } ``` Proxy实例也可以作为其他对象的原型对象。 @@ -877,39 +863,73 @@ fproxy.prototype; // Object.prototype fproxy.foo; // 'Hello, foo' ``` -Proxy支持的拦截操作一览。对于没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。 +下面是Proxy支持的拦截操作一览。 -(1)get(target, propKey, receiver):拦截对象属性的读取,比如`proxy.foo`和`proxy['foo']`,返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。 +对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。 -(2)set(target, propKey, value, receiver):拦截对象属性的设置,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一个布尔值。 +**(1)get(target, propKey, receiver)** -(3)has(target, propKey):拦截`propKey in proxy`的操作,返回一个布尔值。 +拦截对象属性的读取,比如`proxy.foo`和`proxy['foo']`,返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。 -(4)deleteProperty(target, propKey) :拦截`delete proxy[propKey]`的操作,返回一个布尔值。 +**(2)set(target, propKey, value, receiver)** -(5)enumerate(target):拦截`for (var x in proxy)`,返回一个遍历器。 +拦截对象属性的设置,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一个布尔值。 -(6)hasOwn(target, propKey):拦截`proxy.hasOwnProperty('foo')`,返回一个布尔值。 +**(3)has(target, propKey)** -(7)ownKeys(target):拦截`Object.getOwnPropertyNames(proxy)`、`Object.getOwnPropertySymbols(proxy)`、`Object.keys(proxy)`,返回一个数组。该方法返回对象所有自身的属性,而`Object.keys()`仅返回对象可遍历的属性。 +拦截`propKey in proxy`的操作,返回一个布尔值。 -(8)getOwnPropertyDescriptor(target, propKey) :拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象。 +**(4)deleteProperty(target, propKey)** -(9)defineProperty(target, propKey, propDesc):拦截`Object.defineProperty(proxy, propKey, propDesc)`、`Object.defineProperties(proxy, propDescs)`,返回一个布尔值。 +拦截`delete proxy[propKey]`的操作,返回一个布尔值。 -(10)preventExtensions(target):拦截`Object.preventExtensions(proxy)`,返回一个布尔值。 +**(5)enumerate(target)** -(11)getPrototypeOf(target) :拦截`Object.getPrototypeOf(proxy)`,返回一个对象。 +拦截`for (var x in proxy)`,返回一个遍历器。 -(12)isExtensible(target):拦截`Object.isExtensible(proxy)`,返回一个布尔值。 +**6)hasOwn(target, propKey)** -(13)setPrototypeOf(target, proto):拦截`Object.setPrototypeOf(proxy, proto)`,返回一个布尔值。 +拦截`proxy.hasOwnProperty('foo')`,返回一个布尔值。 + +**(7)ownKeys(target)** + +拦截`Object.getOwnPropertyNames(proxy)`、`Object.getOwnPropertySymbols(proxy)`、`Object.keys(proxy)`,返回一个数组。该方法返回对象所有自身的属性,而`Object.keys()`仅返回对象可遍历的属性。 + +**(8)getOwnPropertyDescriptor(target, propKey)** + +拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象。 + +**(9)defineProperty(target, propKey, propDesc)** + +拦截`Object.defineProperty(proxy, propKey, propDesc)`、`Object.defineProperties(proxy, propDescs)`,返回一个布尔值。 + +**(10)preventExtensions(target)** + +拦截`Object.preventExtensions(proxy)`,返回一个布尔值。 + +**(11)getPrototypeOf(target)** + +拦截`Object.getPrototypeOf(proxy)`,返回一个对象。 + +**(12)isExtensible(target)** + +拦截`Object.isExtensible(proxy)`,返回一个布尔值。 + +**(13)setPrototypeOf(target, proto)** + +拦截`Object.setPrototypeOf(proxy, proto)`,返回一个布尔值。 如果目标对象是函数,那么还有两种额外操作可以拦截。 -(14)apply(target, object, args):拦截Proxy实例作为函数调用的操作,比如`proxy(...args)`、`proxy.call(object, ...args)`、`proxy.apply(...)`。 +**(14)apply(target, object, args)** + +拦截Proxy实例作为函数调用的操作,比如`proxy(...args)`、`proxy.call(object, ...args)`、`proxy.apply(...)`。 + +**(15)construct(target, args, proxy)** + +拦截Proxy实例作为构造函数调用的操作,比如new proxy(...args)。 -(15)construct(target, args, proxy):拦截Proxy实例作为构造函数调用的操作,比如new proxy(...args)。 +下面是其中几个重要拦截方法的详细介绍。 ### get() From a0b197300eb98923d10036c195270906d674bab2 Mon Sep 17 00:00:00 2001 From: Milk Lee Date: Thu, 23 Jul 2015 11:38:30 +0800 Subject: [PATCH 0011/1267] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=B8=80=E5=A4=84?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit var person = { get firstName() {} } 应该是取值函数 --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index 6a66fa386..c2cd7f308 100644 --- a/docs/object.md +++ b/docs/object.md @@ -143,7 +143,7 @@ person.sayName.name // "sayName" person.firstName.name // "get firstName" ``` -上面代码中,方法的name属性返回函数名(即方法名)。如果使用了存值函数,则会在方法名前加上get。如果是存值函数,方法名的前面会加上set。 +上面代码中,方法的name属性返回函数名(即方法名)。如果使用了取值函数,则会在方法名前加上get。如果是存值函数,方法名的前面会加上set。 ```javascript var doSomething = function() { From a2c9a4ff738977b9448a6beab48656c26b896628 Mon Sep 17 00:00:00 2001 From: Milk Lee Date: Thu, 23 Jul 2015 11:49:17 +0800 Subject: [PATCH 0012/1267] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20object.md=20?= =?UTF-8?q?=E7=9A=84=E4=B8=80=E5=A4=84=E7=A4=BA=E4=BE=8B=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index 6a66fa386..8a02ca7b4 100644 --- a/docs/object.md +++ b/docs/object.md @@ -445,7 +445,7 @@ a[mySymbol] = 'Hello!'; // 第二种写法 var a = { - [mySymbol]: 123 + [mySymbol]: 'Hello!' }; // 第三种写法 From cd1bb10a1b1e8f5d127314ccda73abdda9f2cb38 Mon Sep 17 00:00:00 2001 From: Milk Lee Date: Thu, 23 Jul 2015 12:34:44 +0800 Subject: [PATCH 0013/1267] =?UTF-8?q?function.md=20=E4=B8=AD=E4=B8=80?= =?UTF-8?q?=E5=A4=84=E7=A4=BA=E4=BE=8B=E7=9A=84=E5=8D=95=E5=BC=95=E5=8F=B7?= =?UTF-8?q?=E5=86=99=E9=94=99=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 示例中写的是中文全角的单引号,正确的应该是英文的单引号 --- docs/function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/function.md b/docs/function.md index 0c35f8c7d..a47cc0755 100644 --- a/docs/function.md +++ b/docs/function.md @@ -497,7 +497,7 @@ const full = ({ first, last }) => first + ' ' + last; // 等同于 function full( person ){ - return person.first + ‘ ‘ + person.name; + return person.first + ' ' + person.name; } ``` From c0b0eed507cb1a8e34d319218f03c839cf6b1cfa Mon Sep 17 00:00:00 2001 From: Milk Lee Date: Thu, 23 Jul 2015 12:43:55 +0800 Subject: [PATCH 0014/1267] =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E4=B8=AD=E7=9A=84=20this=20=E5=B9=B6?= =?UTF-8?q?=E4=B8=8D=E6=98=AF=E6=8C=87=E5=90=91=E5=85=A8=E5=B1=80=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=EF=BC=8C=E8=80=8C=E6=98=AF=E6=8C=87=E5=90=91=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E4=BA=8B=E4=BB=B6=E7=9A=84=E8=8A=82=E7=82=B9=E5=85=83?= =?UTF-8?q?=E7=B4=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ```js document.addEventListener('click',function(){console.log(this === document)}); // true ``` 从上面这段代码可以看出,事件处理函数里的 `this` 并不是指向 `window` 对象。如果你所说的“全局对象”并不是指 `window` 对象,我也觉得应该阐明这里的“全局对象”到底是指什么,以免产生歧义 --- docs/function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/function.md b/docs/function.md index 0c35f8c7d..f4fe970ea 100644 --- a/docs/function.md +++ b/docs/function.md @@ -571,7 +571,7 @@ var handler = { }; ``` -上面代码的init方法中,使用了箭头函数,这导致this绑定handler对象,否则回调函数运行时,this.doSomething这一行会报错,因为此时this指向全局对象。 +上面代码的init方法中,使用了箭头函数,这导致this绑定handler对象,否则回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。 由于this在箭头函数中被绑定,所以不能用call()、apply()、bind()这些方法去改变this的指向。 From 3e6cf8c39f492c0309fd9f6409fc4bda4891c1a0 Mon Sep 17 00:00:00 2001 From: Milk Lee Date: Thu, 23 Jul 2015 13:25:16 +0800 Subject: [PATCH 0015/1267] =?UTF-8?q?iterator.md=20=E4=B8=AD=E5=A4=9A?= =?UTF-8?q?=E5=A4=84=E5=B0=86=20Symbol=20=E9=94=99=E8=AF=AF=E7=9A=84?= =?UTF-8?q?=E5=86=99=E6=88=90=20System?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/iterator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/iterator.md b/docs/iterator.md index d0c3cebe9..56e2c2c8b 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -72,13 +72,13 @@ it.next().value // '2' 上面的例子中,遍历器idMaker函数返回的指针对象,并没有对应的数据结构,或者说遍历器自己描述了一个数据结构出来。 -在ES6中,有些数据结构原生提供遍历器(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了System.iterator属性(详见下文),有些没有。凡是部署了System.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个指针对象。 +在ES6中,有些数据结构原生提供遍历器(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性(详见下文),有些没有。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个指针对象。 如果使用TypeScript的写法,遍历器接口(Iterable)、指针对象(Iterator)和next方法返回值的规格可以描述如下。 ```javascript interface Iterable { - [System.iterator]() : Iterator, + [Symbol.iterator]() : Iterator, } interface Iterator { From 6eef1c75f34a06d9edb8da9c5ff6cfc5a23c2b9a Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Thu, 23 Jul 2015 21:24:25 +0800 Subject: [PATCH 0016/1267] edit iterator --- docs/intro.md | 2 +- docs/iterator.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro.md b/docs/intro.md index f5f90d60d..e625f1290 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -367,4 +367,4 @@ ES7可能包括的功能有: 其他可能包括的功能还有:更精确的数值计算、改善的内存回收、增强的跨站点安全、类型化的更贴近硬件的低级别操作、国际化支持(Internationalization Support)、更多的数据结构等等。 -本书对于那些明确的、或者很有希望列入ES7的功能,尤其是那些Babel已经支持的功能,都将予以介绍。 +本书的写作目标之一,是跟踪ECMAScript语言的最新进展。对于那些明确的、或者很有希望列入ES7的功能,尤其是那些Babel已经支持的功能,都将予以介绍。 diff --git a/docs/iterator.md b/docs/iterator.md index 56e2c2c8b..23d59df0f 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -72,7 +72,7 @@ it.next().value // '2' 上面的例子中,遍历器idMaker函数返回的指针对象,并没有对应的数据结构,或者说遍历器自己描述了一个数据结构出来。 -在ES6中,有些数据结构原生提供遍历器(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性(详见下文),有些没有。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个指针对象。 +在ES6中,有些数据结构原生提供遍历器(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性(详见下文),另外一些数据结构没有。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个指针对象。 如果使用TypeScript的写法,遍历器接口(Iterable)、指针对象(Iterator)和next方法返回值的规格可以描述如下。 From f790a5ea1784a9406c6f5b1cade1e42daddacf6e Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Wed, 29 Jul 2015 16:26:22 +0800 Subject: [PATCH 0017/1267] edit generator/constructor --- docs/generator.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/generator.md b/docs/generator.md index 5ccb29ed5..8a4150c38 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -713,6 +713,44 @@ let obj = { }; ``` +## 构造函数是Generator函数 + +这一节讨论一种特殊情况:构造函数是Generator函数。 + +```javascript +function* F(){ + yield this.x = 2; + yield this.y = 3; +} +``` + +上面代码中,函数F是一个构造函数,又是一个Generator函数。这时,使用new命令就无法生成F的实例了,因为F返回的是一个内部指针。 + +```javascript +'next' in (new F()) +// true +``` + +上面代码中,由于`new F()`返回的是一个Iterator对象,具有next方法,所以上面的表达式为true。 + +那么,这个时候怎么生成对象实例呢? + +我们知道,如果构造函数调用时,没有使用new命令,那么内部的this对象,绑定当前构造函数所在的对象(比如window对象)。因此,可以生成一个空对象,使用bind方法绑定F内部的this。这样,构造函数调用以后,这个空对象就是F的实例对象了。 + +```javascript +var obj = {}; +var f = F.bind(obj)(); + +f.next(); +f.next(); +f.next(); + +console.log(obj); +// { x: 2, y: 3 } +``` + +上面代码中,首先是F内部的this对象绑定obj对象,然后调用它,返回一个Iterator对象。这个对象执行三次next方法(因为F内部有两个yield语句),完成F内部所有代码的运行。这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例。 + ## Generator函数推导 ES7在数组推导的基础上,提出了Generator函数推导(Generator comprehension)。 From 634ab7c5a2e1b9feb62bab0fbbff2522039b893a Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 31 Jul 2015 20:46:08 +0800 Subject: [PATCH 0018/1267] edit docs/class --- docs/class.md | 6 +-- docs/function.md | 20 ++++++++- docs/string.md | 109 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 127 insertions(+), 8 deletions(-) diff --git a/docs/class.md b/docs/class.md index 740f06772..3f143444f 100644 --- a/docs/class.md +++ b/docs/class.md @@ -436,9 +436,9 @@ B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true ``` -上面代码中,子类A的`__proto__`属性指向父类B,子类A的prototype属性的__proto__属性指向父类B的prototype属性。 +上面代码中,子类B的`__proto__`属性指向父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性。 -这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(`__proto__属性`)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。 +这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(`__proto__`属性)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。 ```javascript B.prototype = new A(); @@ -477,7 +477,7 @@ class A extends null { } A.__proto__ === Function.prototype // true -A.prototype.__proto__ === null // true +A.prototype.__proto__ === undefined // true ``` 这种情况与第二种情况非常像。A也是一个普通函数,所以直接继承`Funciton.prototype`。但是,A调用后返回的对象不继承任何方法,所以它的`__proto__`指向`Function.prototype`,即实质上执行了下面的代码。 diff --git a/docs/function.md b/docs/function.md index bece99b6e..d798dcb44 100644 --- a/docs/function.md +++ b/docs/function.md @@ -694,7 +694,7 @@ function f(x){ } ``` -上面代码中,情况一是调用函数g之后,还有别的操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。 +上面代码中,情况一是调用函数g之后,还有赋值操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。 ```javascript function f(x){ @@ -746,6 +746,20 @@ g(3); 这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。 +注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。 + +```javascript +function addOne(a){ + var one = 1; + function inner(b){ + return b + one; + } + return inner(a); +} +``` + +上面的函数不会进行尾调用优化,因为内层函数inner用到了,外层函数addOne的内部变量one。 + ### 尾递归 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。 @@ -774,7 +788,9 @@ function factorial(n, total) { factorial(5, 1) // 120 ``` -由此可见,“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。这就是说,在 ES6 中,只要使用尾递归,就不会发生栈溢出,相对节省内存。 +由此可见,“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有ECMAScript的实现,都必须部署“尾调用优化”。这就是说,在ES6中,只要使用尾递归,就不会发生栈溢出,相对节省内存。 + +目前,只有开启严格模式,尾调用优化才会生效。 ### 递归函数的改写 diff --git a/docs/string.md b/docs/string.md index 9f4874160..c83faf63d 100644 --- a/docs/string.md +++ b/docs/string.md @@ -127,6 +127,8 @@ ES6对这一点做出了改进,只要将码点放入大括号,就能正确 ES6对正则表达式添加了u修饰符,用来正确处理大于\uFFFF的Unicode字符。 +一旦加上u修饰符号,就会修改下面这些正则表达式的行为。 + **(1)点字符** 点(.)字符在正则表达式中,解释为除了换行以外的任意单个字符。对于码点大于0xFFFF的Unicode字符,点字符不能识别,必须加上u修饰符。 @@ -300,7 +302,9 @@ repeat()返回一个新字符串,表示将原字符串重复n次。 ## 正则表达式的y修饰符 -除了u修饰符,ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。它的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始,不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。 +除了u修饰符,ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。 + +y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。 ```javascript var s = "aaa_aa_a"; @@ -328,6 +332,40 @@ r.exec(s) // ["aa_"] 上面代码每次匹配,都是从剩余字符串的头部开始。 +使用lastIndex属性,可以更好地说明y修饰符。 + +```javascript +const REGEX = /a/g; + +REGEX.lastIndex = 2; // 指定从第三个位置y开始搜索 +const match = REGEX.exec('xaya'); + +match.index +// 3 +REGEX.lastIndex +// 4 +REGEX.exec('xaxa') +// null +``` + +上面代码中,lastIndex属性指定每次搜索的开始位置,g修饰符从这个位置开始向后搜索,直到发现匹配为止。 + +y修饰符同样遵守lastIndex属性,但是要求必须在lastIndex指定的位置发现匹配。 + +```javascript +const REGEX = /a/y; + +// 第三个位置y不匹配 +REGEX.lastIndex = 2; +console.log(REGEX.exec('xaya')); // null + +// 第四个位置出现匹配 +REGEX.lastIndex = 3; +const match = REGEX.exec('xaxa'); +match.index // 3 +REGEX.lastIndex // 4 +``` + 进一步说,y修饰符号隐含了头部匹配的标志ˆ。 ```javascript @@ -337,6 +375,32 @@ r.exec(s) // ["aa_"] 上面代码由于不能保证头部匹配,所以返回null。y修饰符的设计本意,就是让头部匹配的标志ˆ在全局匹配中都有效。 +在split方法中使用y修饰符,原字符串必须以分隔符开头。 + +```javascript +// 没有找到匹配 +'x##'.split(/#/y) +// [ 'x##' ] + +// 找到两个匹配 +'##x'.split(/#/y) +// [ '', '', 'x' ] +``` + +后续的分隔符只有紧跟前面的分隔符,才会被识别。 + +```javascript +'#x#'.split(/#/y) +// [ '', 'x#' ] + +'##'.split(/#/y) +// [ '', '', '' ] +``` + +如果同时使用g修饰符和y修饰符,则y修饰符覆盖g修饰符。 + +## 正则表达式的sticky属性 + 与y修饰符相匹配,ES6的正则对象多了sticky属性,表示是否设置了y修饰符。 ```javascript @@ -344,7 +408,46 @@ var r = /hello\d/y; r.sticky // true ``` -## Regexp.escape() +## 正则表达式的flags属性 + +ES6为正则表达式新增了flags属性,会返回正则表达式的修饰符。 + +```javascript +// ES5的source属性 +// 返回正则表达式的正文 +/abc/ig.source +// "abc" + +// ES6的flags属性 +// 返回正则表达式的修饰符 +/abc/ig.flags +// 'gi' +``` + +## RegExp构造函数 + +在ES5中,RegExp构造函数只能接受字符串作为参数。 + +```javascript +var regex = new RegExp("xyz", "i"); +// 等价于 +var regex = /xyz/i; +``` + +ES6允许RegExp构造函数接受正则表达式作为参数,这时会返回一个原有正则表达式的拷贝。 + +```javascript +var regex = new RegExp(/xyz/i); +``` + +如果使用RegExp构造函数的第二个参数指定修饰符,则会修改原有的正则表达式。 + +```javascript +new RegExp(/abc/ig, 'i').flags +// "i" +``` + +## RegExp.escape() 字符串必须转义,才能作为正则模式。 @@ -360,7 +463,7 @@ escapeRegExp(str) 上面代码中,str是一个正常字符串,必须使用反斜杠对其中的特殊字符转义,才能用来作为一个正则匹配的模式。 -已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为[`Regexp.escape()`](https://github.com/benjamingr/RexExp.escape),放入ES7。 +已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为RegExp对象的静态方法[`RegExp.escape()`](https://github.com/benjamingr/RexExp.escape),放入ES7。 ```javascript RegExp.escape("The Quick Brown Fox"); From 5071062c27fb627256210f1cce1c8a2f55883d12 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sat, 1 Aug 2015 15:18:26 +0800 Subject: [PATCH 0019/1267] edit docs/object --- docs/class.md | 4 ++-- docs/object.md | 35 ++++++++++++++++++++++++++++++++- docs/string.md | 2 +- docs/style.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/docs/class.md b/docs/class.md index 3f143444f..cddb77531 100644 --- a/docs/class.md +++ b/docs/class.md @@ -458,7 +458,7 @@ A.prototype.__proto__ === Object.prototype // true 这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。 -第二种特性情况,不存在任何继承。 +第二种特殊情况,不存在任何继承。 ```javascript class A { @@ -499,7 +499,7 @@ Object.getPrototypeOf(ColorPoint) === Point ### 实例的\_\_proto\_\_属性 -父类实例和子类实例的\_\_proto\_\_属性,指向是不一样的。 +子类实例的\_\_proto\_\_属性的\_\_proto\_\_属性,指向父类实例的\_\_proto\_\_属性。也就是说,子类的原型的原型,是父类的原型。 ```javascript var p1 = new Point(2, 3); diff --git a/docs/object.md b/docs/object.md index 73f113e37..229f2d906 100644 --- a/docs/object.md +++ b/docs/object.md @@ -352,12 +352,45 @@ function (obj, proto) { } ``` +下面是一个例子。 + +```javascript +let proto = {}; +let obj = { x: 10 }; +Object.setPrototypeOf(obj, proto); + +proto.y = 20; +proto.z = 40; + +obj.x // 10 +obj.y // 20 +obj.z // 40 +``` + +上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。 + **(3)Object.getPrototypeOf()** 该方法与setPrototypeOf方法配套,用于读取一个对象的prototype对象。 ```javascript -Object.getPrototypeOf(obj) +Object.getPrototypeOf(obj); +``` + +下面是一个例子。 + +```javascript +function Rectangle() { +} + +var rec = new Rectangle(); + +Object.getPrototypeOf(rec) === Rectangle.prototype +// true + +Object.setPrototypeOf(rec, Object.prototype); +Object.getPrototypeOf(rec) === Rectangle.prototype +// false ``` ## Symbol diff --git a/docs/string.md b/docs/string.md index c83faf63d..829369601 100644 --- a/docs/string.md +++ b/docs/string.md @@ -463,7 +463,7 @@ escapeRegExp(str) 上面代码中,str是一个正常字符串,必须使用反斜杠对其中的特殊字符转义,才能用来作为一个正则匹配的模式。 -已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为RegExp对象的静态方法[`RegExp.escape()`](https://github.com/benjamingr/RexExp.escape),放入ES7。 +已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为RegExp对象的静态方法[`RegExp.escape()`](https://github.com/benjamingr/RexExp.escape),放入ES7。2015年7月31日,TC39认为,这个方法有安全风险,又不愿这个方法变得过于复杂,没有同意将其列入ES7,但这不失为一个真实的需求。 ```javascript RegExp.escape("The Quick Brown Fox"); diff --git a/docs/style.md b/docs/style.md index 30634e7b9..f6c8d876c 100644 --- a/docs/style.md +++ b/docs/style.md @@ -460,3 +460,56 @@ const StyleGuide = { export default StyleGuide; ``` + +## ESLint的使用 + +ESLint是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。 + +首先,安装ESLint。 + +```bash +$ npm i -g eslint +``` + +然后,安装ES6插件和预设的Airbnb语法规则。 + +```bash +$ npm i -g babel-eslint eslint-config-airbnb +``` + +最后,在项目的根目录新建一个`.eslintrc`文件。 + +```javascript +{ + "extends": "eslint-config-airbnb" +} +``` + +现在就可以检查当前项目的代码,是否符合规则。 + +假定`index.js`文件的代码如下。 + +```javascript +var unusued = 'I have no purpose!'; + +function greet() { + var message = 'Hello, World!'; + alert(message); +} + +greet(); +``` + +使用ESLint检查这个文件。 + +```bash +$ eslint index.js +index.js + 1:5 error unusued is defined but never used no-unused-vars + 4:5 error Expected indentation of 2 characters but found 4 indent + 5:5 error Expected indentation of 2 characters but found 4 indent + +✖ 3 problems (3 errors, 0 warnings) +``` + +上面代码说明,原文件有三个错误,一个是定义了变量,却没有使用,另外两个是行首缩进为4个空格,而不是规定的2个空格。 From 46268a659d2211b7bc6fda0ffa36375013f29378 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 2 Aug 2015 13:25:05 +0800 Subject: [PATCH 0020/1267] add docs/regex --- docs/reference.md | 8 +- docs/regex.md | 341 ++++++++++++++++++++++++++++++++++++++++++++++ docs/string.md | 302 ++-------------------------------------- docs/style.md | 10 +- sidebar.md | 1 + 5 files changed, 366 insertions(+), 296 deletions(-) create mode 100644 docs/regex.md diff --git a/docs/reference.md b/docs/reference.md index 0ce5bc42f..f9cd35dbd 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -47,13 +47,17 @@ ## 字符串 -- 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 +## 正则 + +- Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 +- Axel Rauschmayer, [New regular expression features in ECMAScript 6](http://www.2ality.com/2015/07/regexp-es6.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()的概念 diff --git a/docs/regex.md b/docs/regex.md new file mode 100644 index 000000000..6eca8d278 --- /dev/null +++ b/docs/regex.md @@ -0,0 +1,341 @@ +# 正则的扩展 + +## RegExp构造函数 + +在ES5中,RegExp构造函数只能接受字符串作为参数。 + +```javascript +var regex = new RegExp("xyz", "i"); +// 等价于 +var regex = /xyz/i; +``` + +ES6允许RegExp构造函数接受正则表达式作为参数,这时会返回一个原有正则表达式的拷贝。 + +```javascript +var regex = new RegExp(/xyz/i); +``` + +如果使用RegExp构造函数的第二个参数指定修饰符,则返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。 + +```javascript +new RegExp(/abc/ig, 'i').flags +// "i" +``` + +## 字符串的正则方法 + +字符串对象共有4个方法,可以使用正则表达式。 + +ES6将这4个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。 + +- String.prototype.match 调用 RegExp.prototype[Symbol.match] +- String.prototype.replace 调用 RegExp.prototype[Symbol.replace] +- String.prototype.search 调用 RegExp.prototype[Symbol.search] +- String.prototype.split 调用 RegExp.prototype[Symbol.split] + +## u修饰符 + +ES6对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于`\uFFFF`的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。 + +```javascript +/^\uD83D/u.test('\uD83D\uDC2A') +// false +/^\uD83D/.test('\uD83D\uDC2A') +// true +``` + +上面代码中,“\uD83D\uDC2A”是一个四个字节的UTF-16编码,代表一个字符。但是,ES5不支持四个字节的UTF-16编码,会将其识别为两个字符,导致第二行代码结果为true。加了u修饰符以后,ES6就会识别其为一个字符,所以第一行代码结果为false。 + +一旦加上u修饰符号,就会修改下面这些正则表达式的行为。 + +**(1)点字符** + +点(.)字符在正则表达式中,含义是除了换行符以外的任意单个字符。对于码点大于`0xFFFF`的Unicode字符,点字符不能识别,必须加上u修饰符。 + +```javascript +var s = "𠮷"; + +/^.$/.test(s) // false +/^.$/u.test(s) // true +``` + +上面代码表示,如果不添加u修饰符,正则表达式就会认为字符串为两个字符,从而匹配失败。 + +**(2)Unicode字符表示法** + +ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上u修饰符,才能识别。 + +```javascript +/\u{61}/.test('a') // false +/\u{61}/u.test('a') // true +/\u{20BB7}/u.test('𠮷') // true +``` + +上面代码表示,如果不加u修饰符,正则表达式无法识别`\u{61}`这种表示法,只会认为这匹配61个连续的u。 + +**(3)量词** + +使用u修饰符后,所有量词都会正确识别大于码点大于`0xFFFF`的Unicode字符。 + +```javascript +/a{2}/.test('aa') // true +/a{2}/u.test('aa') // true +/𠮷{2}/.test('𠮷𠮷') // false +/𠮷{2}/u.test('𠮷𠮷') // true +``` + +另外,只有在使用u修饰符的情况下,Unicode表达式当中的大括号才会被正确解读,否则会被解读为量词。 + +```javascript +/^\u{3}$/.test('uuu') // true +``` + +上面代码中,由于正则表达式没有u修饰符,所以大括号被解读为量词。加上u修饰符,就会被解读为Unicode表达式。 + +**(4)预定义模式** + +u修饰符也影响到预定义模式,能否正确识别码点大于`0xFFFF`的Unicode字符。 + +```javascript +/^\S$/.test('𠮷') // false +/^\S$/u.test('𠮷') // true +``` + +上面代码的`\S`是预定义模式,匹配所有不是空格的字符。只有加了u修饰符,它才能正确匹配码点大于0xFFFF的Unicode字符。 + +利用这一点,可以写出一个正确返回字符串长度的函数。 + +```javascript +function codePointLength(text) { + var result = text.match(/[\s\S]/gu); + return result ? result.length : 0; +} + +var s = "𠮷𠮷"; + +s.length // 4 +codePointLength(s) // 2 +``` + +**(5)i修饰符** + +有些Unicode字符的编码不同,但是字型很相近,比如,\u004B与\u212A都是大写的K。 + +```javascript +/[a-z]/i.test('\u212A') // false +/[a-z]/iu.test('\u212A') // true +``` + +上面代码中,不加u修饰符,就无法识别非规范的K字符。 + +## y修饰符 + +除了u修饰符,ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。 + +y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。 + +```javascript +var s = "aaa_aa_a"; +var r1 = /a+/g; +var r2 = /a+/y; + +r1.exec(s) // ["aaa"] +r2.exec(s) // ["aaa"] + +r1.exec(s) // ["aa"] +r2.exec(s) // null +``` + +上面代码有两个正则表达式,一个使用g修饰符,另一个使用y修饰符。这两个正则表达式各执行了两次,第一次执行的时候,两者行为相同,剩余字符串都是“_aa_a”。由于g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。 + +如果改一下正则表达式,保证每次都能头部匹配,y修饰符就会返回结果了。 + +```javascript +var s = "aaa_aa_a"; +var r = /a+_/y; + +r.exec(s) // ["aaa_"] +r.exec(s) // ["aa_"] +``` + +上面代码每次匹配,都是从剩余字符串的头部开始。 + +使用lastIndex属性,可以更好地说明y修饰符。 + +```javascript +const REGEX = /a/g; + +REGEX.lastIndex = 2; // 指定从第三个位置y开始搜索 +const match = REGEX.exec('xaya'); + +match.index +// 3 +REGEX.lastIndex +// 4 +REGEX.exec('xaxa') +// null +``` + +上面代码中,lastIndex属性指定每次搜索的开始位置,g修饰符从这个位置开始向后搜索,直到发现匹配为止。 + +y修饰符同样遵守lastIndex属性,但是要求必须在lastIndex指定的位置发现匹配。 + +```javascript +const REGEX = /a/y; + +// 第三个位置y不匹配 +REGEX.lastIndex = 2; +console.log(REGEX.exec('xaya')); // null + +// 第四个位置出现匹配 +REGEX.lastIndex = 3; +const match = REGEX.exec('xaxa'); +match.index // 3 +REGEX.lastIndex // 4 +``` + +进一步说,y修饰符号隐含了头部匹配的标志ˆ。 + +```javascript +/b/y.exec("aba") +// null +``` + +上面代码由于不能保证头部匹配,所以返回null。y修饰符的设计本意,就是让头部匹配的标志ˆ在全局匹配中都有效。 + +在split方法中使用y修饰符,原字符串必须以分隔符开头。这也意味着,只要匹配成功,数组的第一个成员肯定是空字符串。 + +```javascript +// 没有找到匹配 +'x##'.split(/#/y) +// [ 'x##' ] + +// 找到两个匹配 +'##x'.split(/#/y) +// [ '', '', 'x' ] +``` + +后续的分隔符只有紧跟前面的分隔符,才会被识别。 + +```javascript +'#x#'.split(/#/y) +// [ '', 'x#' ] + +'##'.split(/#/y) +// [ '', '', '' ] +``` + +下面是字符串对象的replace方法的例子。 + +```javascript +const REGEX = /a/gy; +'aaxa'.replace(REGEX, '-') // '--xa' +``` + +上面代码中,最后一个a因为不是出现下一次匹配的头部,所以不会被替换。 + +如果同时使用g修饰符和y修饰符,则y修饰符覆盖g修饰符。 + +y修饰符的主要作用,是从字符串提取token(词元),y修饰符确保了匹配之间不会有漏掉的字符。 + +```javascript +function tokenize(TOKEN_REGEX, str) { + let result = []; + let match; + while (match = TOKEN_REGEX.exec(str)) { + result.push(match[1]); + } + return result; +} + +const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y; +const TOKEN_G = /\s*(\+|[0-9]+)\s*/g; + +tokenize(TOKEN_Y, '3 + 4') +// [ '3', '+', '4' ] +tokenize(TOKEN_G, '3 + 4') +// [ '3', '+', '4' ] +``` + +上面代码中,如果字符串里面没有非法字符,y修饰符与g修饰符的提取结果是一样的。但是,一旦出现非法字符,两者的行为就不一样了。 + +```javascript +tokenize(TOKEN_Y, '3x + 4') +// [ '3' ] +tokenize(TOKEN_G, '3x + 4') +// [ '3', '+', '4' ] +``` + +上面代码中,g修饰符会忽略非法字符,而y修饰符不会,这样就很容易发现错误。 + +## sticky属性 + +与y修饰符相匹配,ES6的正则对象多了sticky属性,表示是否设置了y修饰符。 + +```javascript +var r = /hello\d/y; +r.sticky // true +``` + +## flags属性 + +ES6为正则表达式新增了flags属性,会返回正则表达式的修饰符。 + +```javascript +// ES5的source属性 +// 返回正则表达式的正文 +/abc/ig.source +// "abc" + +// ES6的flags属性 +// 返回正则表达式的修饰符 +/abc/ig.flags +// 'gi' +``` + +## RegExp.escape() + +字符串必须转义,才能作为正则模式。 + +```javascript +function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); +} + +let str = '/path/to/resource.html?search=query'; +escapeRegExp(str) +// "\/path\/to\/resource\.html\?search=query" +``` + +上面代码中,str是一个正常字符串,必须使用反斜杠对其中的特殊字符转义,才能用来作为一个正则匹配的模式。 + +已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为RegExp对象的静态方法[`RegExp.escape()`](https://github.com/benjamingr/RexExp.escape),放入ES7。2015年7月31日,TC39认为,这个方法有安全风险,又不愿这个方法变得过于复杂,没有同意将其列入ES7,但这不失为一个真实的需求。 + +```javascript +RegExp.escape("The Quick Brown Fox"); +// "The Quick Brown Fox" + +RegExp.escape("Buy it. use it. break it. fix it.") +// "Buy it\. use it\. break it\. fix it\." + +RegExp.escape("(*.*)"); +// "\(\*\.\*\)" +``` + +字符串转义以后,可以使用RegExp构造函数生成正则模式。 + +```javascript +var str = 'hello. how are you?'; +var regex = new RegExp(RegExp.escape(str), 'g'); +assert.equal(String(regex), '/hello\. how are you\?/g'); +``` + +目前,该方法可以用上文的escapeRegExp函数或者垫片模块[`regexp.escape`](https://github.com/ljharb/regexp.escape)实现。 + +```javascript +var escape = require('regexp.escape'); +escape('hi. how are you?') +"hi\\. how are you\\?" +``` + diff --git a/docs/string.md b/docs/string.md index 829369601..2fcf0ed1d 100644 --- a/docs/string.md +++ b/docs/string.md @@ -90,7 +90,7 @@ ES7提供了字符串实例的at方法,可以识别Unicode编号大于0xFFFF ## 字符的Unicode表示法 -JavaScript允许采用“\uxxxx”形式表示一个字符,其中“xxxx”表示字符的码点。 +JavaScript允许采用`\uxxxx`形式表示一个字符,其中“xxxx”表示字符的码点。 ```javascript "\u0061" @@ -100,119 +100,43 @@ JavaScript允许采用“\uxxxx”形式表示一个字符,其中“xxxx”表 但是,这种表示法只限于\u0000——\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表达。 ```javascript - "\uD842\uDFB7" // "𠮷" "\u20BB7" // " 7" - ``` -上面代码表示,如果直接在“\u”后面跟上超过0xFFFF的数值(比如\u20BB7),JavaScript会理解成“\u20BB+7”。由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。 +上面代码表示,如果直接在“\u”后面跟上超过`0xFFFF`的数值(比如\u20BB7),JavaScript会理解成“\u20BB+7”。由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。 ES6对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。 ```javascript - "\u{20BB7}" // "𠮷" "\u{41}\u{42}\u{43}" // "ABC" -``` - -## 正则表达式的u修饰符 - -ES6对正则表达式添加了u修饰符,用来正确处理大于\uFFFF的Unicode字符。 - -一旦加上u修饰符号,就会修改下面这些正则表达式的行为。 - -**(1)点字符** - -点(.)字符在正则表达式中,解释为除了换行以外的任意单个字符。对于码点大于0xFFFF的Unicode字符,点字符不能识别,必须加上u修饰符。 - -```javascript - -var s = "𠮷"; - -/^.$/.test(s) // false -/^.$/u.test(s) // true - -``` - -上面代码表示,如果不添加u修饰符,正则表达式就会认为字符串为两个字符,从而匹配失败。 - -**(2)Unicode字符表示法** - -ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上u修饰符,才能识别。 - -```javascript - -/\u{61}/.test('a') // false -/\u{61}/u.test('a') // true -/\u{20BB7}/u.test('𠮷') // true - -``` - -上面代码表示,如果不加u修饰符,正则表达式无法识别`\u{61}`这种表示法,只会认为这匹配61个连续的u。 - -**(3)量词** - -使用u修饰符后,所有量词都会正确识别大于码点大于0xFFFF的Unicode字符。 - -```javascript - -/a{2}/.test('aa') // true -/a{2}/u.test('aa') // true -/𠮷{2}/.test('𠮷𠮷') // false -/𠮷{2}/u.test('𠮷𠮷') // true - -``` - -**(4)预定义模式** - -u修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的Unicode字符。 - -```javascript - -/^\S$/.test('𠮷') // false -/^\S$/u.test('𠮷') - -``` - -上面代码的`\S`是预定义模式,匹配所有不是空格的字符。只有加了u修饰符,它才能正确匹配码点大于0xFFFF的Unicode字符。 - -利用这一点,可以写出一个正确返回字符串长度的函数。 - -```javascript - -function codePointLength(text) { - var result = text.match(/[\s\S]/gu); - return result ? result.length : 0; -} - -var s = "𠮷𠮷"; - -s.length // 4 -codePointLength(s) // 2 +let hello = 123; +hell\u{6F} // 123 +'\u{1F680}' === '\uD83D\uDE80' +// true ``` -**(5)i修饰符** +上面代码中,最后一个例子表明,大括号表示法与四字节的UTF-16编码是等价的。 -有些Unicode字符的编码不同,但是字型很相近,比如,\u004B与\u212A都是大写的K。 +有了这种表示法之后,JavaScript共有5种方法可以表示一个字符。 ```javascript - -/[a-z]/i.test('\u212A') // false -/[a-z]/iu.test('\u212A') // true - +'\z' === 'z' // true +'\172' === 'z' // true +'\x7A' === 'z' // true +'\u007A' === 'z' // true +'\u{7A}' === 'z' // true ``` -上面代码中,不加u修饰符,就无法识别非规范的K字符。 - ## normalize() 为了表示语调和重音符号,Unicode提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。 @@ -233,10 +157,8 @@ codePointLength(s) // 2 ES6提供String.prototype.normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为Unicode正规化。 ```javascript - '\u01D1'.normalize() === '\u004F\u030C'.normalize() // true - ``` normalize方法可以接受四个参数。 @@ -266,25 +188,21 @@ normalize方法可以接受四个参数。 - **endsWith()**:返回布尔值,表示参数字符串是否在源字符串的尾部。 ```javascript - var s = "Hello world!"; s.startsWith("Hello") // true s.endsWith("!") // true s.includes("o") // true - ``` 这三个方法都支持第二个参数,表示开始搜索的位置。 ```javascript - var s = "Hello world!"; s.startsWith("world", 6) // true s.endsWith("Hello", 5) // true s.includes("Hello", 6) // false - ``` 上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。 @@ -294,202 +212,8 @@ s.includes("Hello", 6) // false repeat()返回一个新字符串,表示将原字符串重复n次。 ```javascript - "x".repeat(3) // "xxx" "hello".repeat(2) // "hellohello" - -``` - -## 正则表达式的y修饰符 - -除了u修饰符,ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。 - -y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。 - -```javascript -var s = "aaa_aa_a"; -var r1 = /a+/g; -var r2 = /a+/y; - -r1.exec(s) // ["aaa"] -r2.exec(s) // ["aaa"] - -r1.exec(s) // ["aa"] -r2.exec(s) // null -``` - -上面代码有两个正则表达式,一个使用g修饰符,另一个使用y修饰符。这两个正则表达式各执行了两次,第一次执行的时候,两者行为相同,剩余字符串都是“_aa_a”。由于g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。 - -如果改一下正则表达式,保证每次都能头部匹配,y修饰符就会返回结果了。 - -```javascript -var s = "aaa_aa_a"; -var r = /a+_/y; - -r.exec(s) // ["aaa_"] -r.exec(s) // ["aa_"] -``` - -上面代码每次匹配,都是从剩余字符串的头部开始。 - -使用lastIndex属性,可以更好地说明y修饰符。 - -```javascript -const REGEX = /a/g; - -REGEX.lastIndex = 2; // 指定从第三个位置y开始搜索 -const match = REGEX.exec('xaya'); - -match.index -// 3 -REGEX.lastIndex -// 4 -REGEX.exec('xaxa') -// null -``` - -上面代码中,lastIndex属性指定每次搜索的开始位置,g修饰符从这个位置开始向后搜索,直到发现匹配为止。 - -y修饰符同样遵守lastIndex属性,但是要求必须在lastIndex指定的位置发现匹配。 - -```javascript -const REGEX = /a/y; - -// 第三个位置y不匹配 -REGEX.lastIndex = 2; -console.log(REGEX.exec('xaya')); // null - -// 第四个位置出现匹配 -REGEX.lastIndex = 3; -const match = REGEX.exec('xaxa'); -match.index // 3 -REGEX.lastIndex // 4 -``` - -进一步说,y修饰符号隐含了头部匹配的标志ˆ。 - -```javascript -/b/y.exec("aba") -// null -``` - -上面代码由于不能保证头部匹配,所以返回null。y修饰符的设计本意,就是让头部匹配的标志ˆ在全局匹配中都有效。 - -在split方法中使用y修饰符,原字符串必须以分隔符开头。 - -```javascript -// 没有找到匹配 -'x##'.split(/#/y) -// [ 'x##' ] - -// 找到两个匹配 -'##x'.split(/#/y) -// [ '', '', 'x' ] -``` - -后续的分隔符只有紧跟前面的分隔符,才会被识别。 - -```javascript -'#x#'.split(/#/y) -// [ '', 'x#' ] - -'##'.split(/#/y) -// [ '', '', '' ] -``` - -如果同时使用g修饰符和y修饰符,则y修饰符覆盖g修饰符。 - -## 正则表达式的sticky属性 - -与y修饰符相匹配,ES6的正则对象多了sticky属性,表示是否设置了y修饰符。 - -```javascript -var r = /hello\d/y; -r.sticky // true -``` - -## 正则表达式的flags属性 - -ES6为正则表达式新增了flags属性,会返回正则表达式的修饰符。 - -```javascript -// ES5的source属性 -// 返回正则表达式的正文 -/abc/ig.source -// "abc" - -// ES6的flags属性 -// 返回正则表达式的修饰符 -/abc/ig.flags -// 'gi' -``` - -## RegExp构造函数 - -在ES5中,RegExp构造函数只能接受字符串作为参数。 - -```javascript -var regex = new RegExp("xyz", "i"); -// 等价于 -var regex = /xyz/i; -``` - -ES6允许RegExp构造函数接受正则表达式作为参数,这时会返回一个原有正则表达式的拷贝。 - -```javascript -var regex = new RegExp(/xyz/i); -``` - -如果使用RegExp构造函数的第二个参数指定修饰符,则会修改原有的正则表达式。 - -```javascript -new RegExp(/abc/ig, 'i').flags -// "i" -``` - -## RegExp.escape() - -字符串必须转义,才能作为正则模式。 - -```javascript -function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); -} - -let str = '/path/to/resource.html?search=query'; -escapeRegExp(str) -// "\/path\/to\/resource\.html\?search=query" -``` - -上面代码中,str是一个正常字符串,必须使用反斜杠对其中的特殊字符转义,才能用来作为一个正则匹配的模式。 - -已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为RegExp对象的静态方法[`RegExp.escape()`](https://github.com/benjamingr/RexExp.escape),放入ES7。2015年7月31日,TC39认为,这个方法有安全风险,又不愿这个方法变得过于复杂,没有同意将其列入ES7,但这不失为一个真实的需求。 - -```javascript -RegExp.escape("The Quick Brown Fox"); -// "The Quick Brown Fox" - -RegExp.escape("Buy it. use it. break it. fix it.") -// "Buy it\. use it\. break it\. fix it\." - -RegExp.escape("(*.*)"); -// "\(\*\.\*\)" -``` - -字符串转义以后,可以使用RegExp构造函数生成正则模式。 - -```javascript -var str = 'hello. how are you?'; -var regex = new RegExp(RegExp.escape(str), 'g'); -assert.equal(String(regex), '/hello\. how are you\?/g'); -``` - -目前,该方法可以用上文的escapeRegExp函数或者垫片模块[`regexp.escape`](https://github.com/ljharb/regexp.escape)实现。 - -```javascript -var escape = require('regexp.escape'); -escape('hi. how are you?') -"hi\\. how are you\\?" ``` ## 模板字符串 diff --git a/docs/style.md b/docs/style.md index f6c8d876c..e43db80ae 100644 --- a/docs/style.md +++ b/docs/style.md @@ -471,13 +471,13 @@ ESLint是一个语法规则和代码风格的检查工具,可以用来保证 $ npm i -g eslint ``` -然后,安装ES6插件和预设的Airbnb语法规则。 +然后,安装Airbnb语法规则。 ```bash -$ npm i -g babel-eslint eslint-config-airbnb +$ npm i -g eslint-config-airbnb ``` -最后,在项目的根目录新建一个`.eslintrc`文件。 +最后,在项目的根目录下新建一个`.eslintrc`文件,配置ESLint。 ```javascript { @@ -485,9 +485,9 @@ $ npm i -g babel-eslint eslint-config-airbnb } ``` -现在就可以检查当前项目的代码,是否符合规则。 +现在就可以检查,当前项目的代码是否符合预设的规则。 -假定`index.js`文件的代码如下。 +`index.js`文件的代码如下。 ```javascript var unusued = 'I have no purpose!'; diff --git a/sidebar.md b/sidebar.md index 83724af38..c655486d3 100644 --- a/sidebar.md +++ b/sidebar.md @@ -10,6 +10,7 @@ 1. [let和const命令](#docs/let) 1. [变量的解构赋值](#docs/destructuring) 1. [字符串的扩展](#docs/string) +1. [正则的扩展](#docs/regex) 1. [数值的扩展](#docs/number) 1. [数组的扩展](#docs/array) 1. [对象的扩展](#docs/object) From 37413140d690d67c79dbdb23e4cc6177e849ed23 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Sun, 2 Aug 2015 14:16:48 +0800 Subject: [PATCH 0021/1267] edit docs/string --- docs/string.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/string.md b/docs/string.md index 2fcf0ed1d..f9bd649d4 100644 --- a/docs/string.md +++ b/docs/string.md @@ -37,7 +37,7 @@ codePointAt方法是测试一个字符由两个字节还是由四个字节组成 ```javascript function is32Bit(c) { - return c.codePointAt(0) > 0xFFFF; + return c.codePointAt(0) > 0xFFFF; } is32Bit("𠮷") // true @@ -49,10 +49,8 @@ is32Bit("a") // false ES5提供String.fromCharCode方法,用于从码点返回对应字符,但是这个方法不能识别辅助平面的字符(编号大于0xFFFF)。 ```javascript - String.fromCharCode(0x20BB7) // "ஷ" - ``` 上面代码中,最后返回码点U+0BB7对应的字符,而不是码点U+20BB7对应的字符。 @@ -60,32 +58,28 @@ String.fromCharCode(0x20BB7) ES6提供了String.fromCodePoint方法,可以识别0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。 ```javascript - String.fromCodePoint(0x20BB7) // "𠮷" - ``` 注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。 -## String.prototype.at() +## at() -ES5提供String.prototype.charAt方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF的字符。 +ES5对字符串对象提供charAt方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF的字符。 ```javascript - -'𠮷'.charAt(0) -// '\uD842' - +'abc'.charAt(0) // "a" +'𠮷'.charAt(0) // "\uD842" ``` 上面代码中,charAt方法返回的是UTF-16编码的第一个字节,实际上是无法显示的。 -ES7提供了字符串实例的at方法,可以识别Unicode编号大于0xFFFF的字符,返回正确的字符。 +ES7提供了字符串实例的at方法,可以识别Unicode编号大于0xFFFF的字符,返回正确的字符。Chrome浏览器已经支持该方法。 ```javascript -'𠮷'.at(0) -// '𠮷' +'abc'.at(0) // "a" +'𠮷'.at(0) // "𠮷" ``` ## 字符的Unicode表示法 From 61aa7c805c4ca17f539c30c7a4de4321c1ff516c Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Mon, 3 Aug 2015 13:33:50 +0800 Subject: [PATCH 0022/1267] edit regex --- docs/regex.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/regex.md b/docs/regex.md index 6eca8d278..f6edb244f 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -310,7 +310,7 @@ escapeRegExp(str) 上面代码中,str是一个正常字符串,必须使用反斜杠对其中的特殊字符转义,才能用来作为一个正则匹配的模式。 -已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为RegExp对象的静态方法[`RegExp.escape()`](https://github.com/benjamingr/RexExp.escape),放入ES7。2015年7月31日,TC39认为,这个方法有安全风险,又不愿这个方法变得过于复杂,没有同意将其列入ES7,但这不失为一个真实的需求。 +已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为RegExp对象的静态方法[RegExp.escape()](https://github.com/benjamingr/RexExp.escape),放入ES7。2015年7月31日,TC39认为,这个方法有安全风险,又不愿这个方法变得过于复杂,没有同意将其列入ES7,但这不失为一个真实的需求。 ```javascript RegExp.escape("The Quick Brown Fox"); @@ -331,7 +331,7 @@ var regex = new RegExp(RegExp.escape(str), 'g'); assert.equal(String(regex), '/hello\. how are you\?/g'); ``` -目前,该方法可以用上文的escapeRegExp函数或者垫片模块[`regexp.escape`](https://github.com/ljharb/regexp.escape)实现。 +目前,该方法可以用上文的escapeRegExp函数或者垫片模块[regexp.escape](https://github.com/ljharb/regexp.escape)实现。 ```javascript var escape = require('regexp.escape'); From be427eb47fc2875c068759c2de259541a6007b98 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 4 Aug 2015 09:19:51 +0800 Subject: [PATCH 0023/1267] edit string --- docs/let.md | 39 ++++++++++++++- docs/regex.md | 2 +- docs/string.md | 132 ++++++++++++++++++++++++------------------------- 3 files changed, 103 insertions(+), 70 deletions(-) diff --git a/docs/let.md b/docs/let.md index 26dfb81d0..3073d01a7 100644 --- a/docs/let.md +++ b/docs/let.md @@ -184,13 +184,50 @@ function func(arg) { ## 块级作用域 +### 为什么需要块级作用域? + +ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。 + +第一种场景,内层变量可能会覆盖外层变量。 + +```javascript +var tmp = new Date(); + +function f(){ + console.log(tmp); + if (false){ + var tmp = "hello world"; + } +} + +f() // 没有输出 +``` + +上面代码中,函数f执行后没有任何输出,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。 + +第二种场景,用来计数的循环变量泄露为全局变量。 + +```javascript +var s = 'hello'; + +for (var i = 0; i < s.length; i++){ + console.log(s[i]); +} + +console.log(i); // 5 +``` + +上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。 + +### ES6的块级作用域 + let实际上为JavaScript新增了块级作用域。 ```javascript function f1() { let n = 5; if (true) { - let n = 10; + let n = 10; } console.log(n); // 5 } diff --git a/docs/regex.md b/docs/regex.md index f6edb244f..7fddbc4a3 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -25,7 +25,7 @@ new RegExp(/abc/ig, 'i').flags ## 字符串的正则方法 -字符串对象共有4个方法,可以使用正则表达式。 +字符串对象共有4个方法,可以使用正则表达式:match()、replace()、search()和split()。 ES6将这4个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。 diff --git a/docs/string.md b/docs/string.md index f9bd649d4..fe4fa96bc 100644 --- a/docs/string.md +++ b/docs/string.md @@ -2,46 +2,53 @@ ES6加强了对Unicode的支持,并且扩展了字符串对象。 -## codePointAt() +## 字符的Unicode表示法 -JavaScript内部,字符以UTF-16的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode码点大于0xFFFF的字符),JavaScript会认为它们是两个字符。 +JavaScript允许采用`\uxxxx`形式表示一个字符,其中“xxxx”表示字符的码点。 ```javascript -var s = "𠮷"; +"\u0061" +// "a" +``` -s.length // 2 -s.charAt(0) // '' -s.charAt(1) // '' -s.charCodeAt(0) // 55362 -s.charCodeAt(1) // 57271 +但是,这种表示法只限于\u0000——\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表达。 + +```javascript +"\uD842\uDFB7" +// "𠮷" + +"\u20BB7" +// " 7" ``` -上面代码中,汉字“𠮷”的码点是0x20BB7,UTF-16编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。对于这种4个字节的字符,JavaScript不能正确处理,字符串长度会误判为2,而且charAt方法无法读取字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。 +上面代码表示,如果直接在“\u”后面跟上超过`0xFFFF`的数值(比如\u20BB7),JavaScript会理解成“\u20BB+7”。由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。 -ES6提供了codePointAt方法,能够正确处理4个字节储存的字符,返回一个字符的码点。 +ES6对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。 ```javascript -var s = "𠮷a"; +"\u{20BB7}" +// "𠮷" -s.codePointAt(0) // 134071 -s.codePointAt(1) // 57271 +"\u{41}\u{42}\u{43}" +// "ABC" -s.charCodeAt(2) // 97 -``` +let hello = 123; +hell\u{6F} // 123 -codePointAt方法的参数,是字符在字符串中的位置(从0开始)。上面代码中,JavaScript将“𠮷a”视为三个字符,codePointAt方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点134071(即十六进制的20BB7)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt方法的结果与charCodeAt方法相同。 +'\u{1F680}' === '\uD83D\uDE80' +// true +``` -总之,codePointAt方法会正确返回四字节的UTF-16字符的码点。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt方法相同。 +上面代码中,最后一个例子表明,大括号表示法与四字节的UTF-16编码是等价的。 -codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。 +有了这种表示法之后,JavaScript共有5种方法可以表示一个字符。 ```javascript -function is32Bit(c) { - return c.codePointAt(0) > 0xFFFF; -} - -is32Bit("𠮷") // true -is32Bit("a") // false +'\z' === 'z' // true +'\172' === 'z' // true +'\x7A' === 'z' // true +'\u007A' === 'z' // true +'\u{7A}' === 'z' // true ``` ## String.fromCodePoint() @@ -64,71 +71,64 @@ String.fromCodePoint(0x20BB7) 注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。 -## at() +## codePointAt() -ES5对字符串对象提供charAt方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF的字符。 +JavaScript内部,字符以UTF-16的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode码点大于0xFFFF的字符),JavaScript会认为它们是两个字符。 ```javascript -'abc'.charAt(0) // "a" -'𠮷'.charAt(0) // "\uD842" +var s = "𠮷"; + +s.length // 2 +s.charAt(0) // '' +s.charAt(1) // '' +s.charCodeAt(0) // 55362 +s.charCodeAt(1) // 57271 ``` -上面代码中,charAt方法返回的是UTF-16编码的第一个字节,实际上是无法显示的。 +上面代码中,汉字“𠮷”的码点是0x20BB7,UTF-16编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。对于这种4个字节的字符,JavaScript不能正确处理,字符串长度会误判为2,而且charAt方法无法读取字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。 -ES7提供了字符串实例的at方法,可以识别Unicode编号大于0xFFFF的字符,返回正确的字符。Chrome浏览器已经支持该方法。 +ES6提供了codePointAt方法,能够正确处理4个字节储存的字符,返回一个字符的码点。 ```javascript -'abc'.at(0) // "a" -'𠮷'.at(0) // "𠮷" -``` - -## 字符的Unicode表示法 +var s = "𠮷a"; -JavaScript允许采用`\uxxxx`形式表示一个字符,其中“xxxx”表示字符的码点。 +s.codePointAt(0) // 134071 +s.codePointAt(1) // 57271 -```javascript -"\u0061" -// "a" +s.charCodeAt(2) // 97 ``` -但是,这种表示法只限于\u0000——\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表达。 +codePointAt方法的参数,是字符在字符串中的位置(从0开始)。上面代码中,JavaScript将“𠮷a”视为三个字符,codePointAt方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点134071(即十六进制的20BB7)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt方法的结果与charCodeAt方法相同。 + +总之,codePointAt方法会正确返回四字节的UTF-16字符的码点。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt方法相同。 + +codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。 ```javascript -"\uD842\uDFB7" -// "𠮷" +function is32Bit(c) { + return c.codePointAt(0) > 0xFFFF; +} -"\u20BB7" -// " 7" +is32Bit("𠮷") // true +is32Bit("a") // false ``` -上面代码表示,如果直接在“\u”后面跟上超过`0xFFFF`的数值(比如\u20BB7),JavaScript会理解成“\u20BB+7”。由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。 +## at() -ES6对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。 +ES5对字符串对象提供charAt方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF的字符。 ```javascript -"\u{20BB7}" -// "𠮷" - -"\u{41}\u{42}\u{43}" -// "ABC" - -let hello = 123; -hell\u{6F} // 123 - -'\u{1F680}' === '\uD83D\uDE80' -// true +'abc'.charAt(0) // "a" +'𠮷'.charAt(0) // "\uD842" ``` -上面代码中,最后一个例子表明,大括号表示法与四字节的UTF-16编码是等价的。 +上面代码中,charAt方法返回的是UTF-16编码的第一个字节,实际上是无法显示的。 -有了这种表示法之后,JavaScript共有5种方法可以表示一个字符。 +ES7提供了字符串实例的at方法,可以识别Unicode编号大于0xFFFF的字符,返回正确的字符。Chrome浏览器已经支持该方法。 ```javascript -'\z' === 'z' // true -'\172' === 'z' // true -'\x7A' === 'z' // true -'\u007A' === 'z' // true -'\u{7A}' === 'z' // true +'abc'.at(0) // "a" +'𠮷'.at(0) // "𠮷" ``` ## normalize() @@ -138,12 +138,10 @@ hell\u{6F} // 123 这两种表示方法,在视觉和语义上都等价,但是JavaScript不能识别。 ```javascript - '\u01D1'==='\u004F\u030C' //false '\u01D1'.length // 1 '\u004F\u030C'.length // 2 - ``` 上面代码表示,JavaScript将合成字符视为两个字符,导致两种表示方法不相等。 @@ -163,10 +161,8 @@ normalize方法可以接受四个参数。 - NFKD,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。 ```javascript - '\u004F\u030C'.normalize('NFC').length // 1 '\u004F\u030C'.normalize('NFD').length // 2 - ``` 上面代码表示,NFC参数返回字符的合成形式,NFD参数返回字符的分解形式。 From 4960a1808f3d59e99072d761a47cec095938930d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 7 Aug 2015 07:39:40 +0800 Subject: [PATCH 0024/1267] =?UTF-8?q?Symbol=E5=88=97=E4=B8=BA=E5=8D=95?= =?UTF-8?q?=E7=8B=AC=E7=9A=84=E7=AB=A0=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/iterator.md | 31 ++- docs/let.md | 2 +- docs/object.md | 400 --------------------------------------- docs/reference.md | 1 + docs/symbol.md | 468 ++++++++++++++++++++++++++++++++++++++++++++++ sidebar.md | 1 + 6 files changed, 497 insertions(+), 406 deletions(-) create mode 100644 docs/symbol.md diff --git a/docs/iterator.md b/docs/iterator.md index 23d59df0f..ea4e0f529 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -298,14 +298,35 @@ let arr = ['b', 'c']; let arr = [...iterable]; ``` -**(3)其他场合** +**(3)yield* ** -以下场合也会用到默认的iterator接口,可以查阅相关章节。 +yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。 + +```javascript +let generator = function* () { + yield 1; + yield* [2,3,4]; //use an iterable, is looped, and added as yields + yield 5; +}; + +var iterator = generator(); + +iterator.next() // { value: 1, done: false } +iterator.next() // { value: 2, done: false } +iterator.next() // { value: 3, done: false } +iterator.next() // { value: 4, done: false } +iterator.next() // { value: 5, done: false } +iterator.next() // { value: undefined, done: true } +``` + +**(4)其他场合** + +由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。 -- yield* - Array.from() -- Map(), Set(), WeakMap(), WeakSet() -- Promise.all(), Promise.race() +- Map(), Set(), WeakMap(), WeakSet()(比如`new Map([['a',1],['b',2]])`) +- Promise.all() +- Promise.race() ## 原生具备Iterator接口的数据结构 diff --git a/docs/let.md b/docs/let.md index 3073d01a7..5dde6c74f 100644 --- a/docs/let.md +++ b/docs/let.md @@ -113,7 +113,7 @@ if (true) { 有些“死区”比较隐蔽,不太容易发现。 ```javascript -function bar(x=y, y=2) { +function bar(x = y, y = 2) { return [x, y]; } diff --git a/docs/object.md b/docs/object.md index 229f2d906..dc15a382d 100644 --- a/docs/object.md +++ b/docs/object.md @@ -393,406 +393,6 @@ Object.getPrototypeOf(rec) === Rectangle.prototype // false ``` -## Symbol - -### 概述 - -在ES5中,对象的属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。 - -ES6引入了一种新的原始数据类型Symbol,表示独一无二的ID。它通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 - -```javascript -let s = Symbol(); - -typeof s -// "symbol" -``` - -上面代码中,变量s就是一个独一无二的ID。typeof运算符的结果,表明变量s是Symbol数据类型,而不是字符串之类的其他类型。 - -注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。 - -Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。 - -```javascript -var s1 = Symbol('foo'); -var s2 = Symbol('bar'); - -s1 // Symbol(foo) -s2 // Symbol(bar) - -s1.toString() // "Symbol(foo)" -s2.toString() // "Symbol(bar)" -``` - -上面代码中,s1和s2是两个Symbol值。如果不加参数,它们在控制台的输出都是`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类型的值不能与其他类型的值进行运算,会报错。 - -```javascript -var sym = Symbol('My symbol'); - -"your symbol is " + sym -// TypeError: can't convert symbol to string -`your symbol is ${sym}` -// TypeError: can't convert symbol to string -``` - -但是,Symbol类型的值可以转为字符串。 - -```javascript -var sym = Symbol('My symbol'); - -String(sym) // 'Symbol(My symbol)' -sym.toString() // 'Symbol(My symbol)' -``` - -### 作为属性名的Symbol - -由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。 - -```javascript -var mySymbol = Symbol(); - -// 第一种写法 -var a = {}; -a[mySymbol] = 'Hello!'; - -// 第二种写法 -var a = { - [mySymbol]: 'Hello!' -}; - -// 第三种写法 -var a = {}; -Object.defineProperty(a, mySymbol, { value: 'Hello!' }); - -// 以上写法都得到同样结果 -a[mySymbol] // "Hello!" -``` - -上面代码通过方括号结构和Object.defineProperty,将对象的属性名指定为一个Symbol值。 - -注意,Symbol值作为对象属性名时,不能用点运算符。 - -```javascript -var mySymbol = Symbol(); -var a = {}; - -a.mySymbol = 'Hello!'; -a[mySymbol] // undefined -a['mySymbol'] // "Hello!" -``` - -上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个Symbol值。 - -同理,在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。 - -```javascript -let s = Symbol(); - -let obj = { - [s]: function (arg) { ... } -}; - -obj[s](123); -``` - -上面代码中,如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个Symbol值。 - -采用增强的对象写法,上面代码的obj对象可以写得更简洁一些。 - -```javascript -let obj = { - [s](arg) { ... } -}; -``` - -Symbol类型还可以用于定义一组常量,保证这组常量的值都是不相等的。 - -```javascript -log.levels = { - DEBUG: Symbol('debug'), - INFO: Symbol('info'), - WARN: Symbol('warn'), -}; -log(log.levels.DEBUG, 'debug message'); -log(log.levels.INFO, 'info message'); -``` - -还有一点需要注意,Symbol值作为属性名时,该属性还是公开属性,不是私有属性。 - -### 属性名的遍历 - -Symbol作为属性名,该属性不会出现在for...in、for...of循环中,也不会被`Object.keys()`、`Object.getOwnPropertyNames()`返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。 - -Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。 - -```javascript -var obj = {}; -var a = Symbol('a'); -var b = Symbol.for('b'); - -obj[a] = 'Hello'; -obj[b] = 'World'; - -var objectSymbols = Object.getOwnPropertySymbols(obj); - -objectSymbols -// [Symbol(a), Symbol(b)] -``` - -下面是另一个例子,Object.getOwnPropertySymbols方法与for...in循环、Object.getOwnPropertyNames方法进行对比的例子。 - -```javascript -var obj = {}; - -var foo = Symbol("foo"); - -Object.defineProperty(obj, foo, { - value: "foobar", -}); - -for (var i in obj) { - console.log(i); // 无输出 -} - -Object.getOwnPropertyNames(obj) -// [] - -Object.getOwnPropertySymbols(obj) -// [Symbol(foo)] -``` - -上面代码中,使用Object.getOwnPropertyNames方法得不到Symbol属性名,需要使用Object.getOwnPropertySymbols方法。 - -另一个新的API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名。 - -```javascript -let obj = { - [Symbol('my_key')]: 1, - enum: 2, - nonEnum: 3 -}; - -Reflect.ownKeys(obj) -// [Symbol(my_key), 'enum', 'nonEnum'] -``` - -由于以Symbol值作为名称的属性,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。 - -```javascript -var size = Symbol('size'); - -class Collection { - constructor() { - this[size] = 0; - } - - add(item) { - this[this[size]] = item; - this[size]++; - } - - static sizeOf(instance) { - return instance[size]; - } -} - -var x = new Collection(); -Collection.sizeOf(x) // 0 - -x.add('foo'); -Collection.sizeOf(x) // 1 - -Object.keys(x) // ['0'] -Object.getOwnPropertyNames(x) // ['0'] -Object.getOwnPropertySymbols(x) // [Symbol(size)] -``` - -上面代码中,对象x的size属性是一个Symbol值,所以`Object.keys(x)`、`Object.getOwnPropertyNames(x)`都无法获取它。这就造成了一种非私有的内部方法的效果。 - -### Symbol.for(),Symbol.keyFor() - -有时,我们希望重新使用同一个Symbol值,`Symbol.for`方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。 - -```javascript -var s1 = Symbol.for('foo'); -var s2 = Symbol.for('foo'); - -s1 === s2 // true -``` - -上面代码中,s1和s2都是Symbol值,但是它们都是同样参数的`Symbol.for`方法生成的,所以实际上是同一个值。 - -`Symbol.for()`与`Symbol()`这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。`Symbol.for()`不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用`Symbol.for("cat")`30次,每次都会返回同一个Symbol值,但是调用`Symbol("cat")`30次,会返回30个不同的Symbol值。 - -```javascript -Symbol.for("bar") === Symbol.for("bar") -// true - -Symbol("bar") === Symbol("bar") -// false -``` - -上面代码中,由于`Symbol()`写法没有登记机制,所以每次调用都会返回一个不同的值。 - -Symbol.keyFor方法返回一个已登记的Symbol类型值的key。 - -```javascript -var s1 = Symbol.for("foo"); -Symbol.keyFor(s1) // "foo" - -var s2 = Symbol("foo"); -Symbol.keyFor(s2) // undefined -``` - -上面代码中,变量s2属于未登记的Symbol值,所以返回undefined。 - -需要注意的是,`Symbol.for`为Symbol值登记的名字,是全局环境的,可以在不同的iframe或service worker中取到同一个值。 - -```javascript -iframe = document.createElement('iframe'); -iframe.src = String(window.location); -document.body.appendChild(iframe); - -iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo') -// true -``` - -上面代码中,iframe窗口生成的Symbol值,可以在主页面得到。 - -### 内置的Symbol值 - -除了定义自己使用的Symbol值以外,ES6还提供一些内置的Symbol值,指向语言内部使用的方法。 - -**(1)Symbol.hasInstance** - -对象的Symbol.hasInstance属性,指向一个内部方法。该对象使用instanceof运算符时,会调用这个方法,判断该对象是否为某个构造函数的实例。比如,`foo instanceof Foo`在语言内部,实际调用的是`Foo[Symbol.hasInstance](foo)`。 - -**(2)Symbol.isConcatSpreadable** - -对象的Symbol.isConcatSpreadable属性,指向一个方法。该对象使用Array.prototype.concat()时,会调用这个方法,返回一个布尔值,表示该对象是否可以扩展成数组。 - -**(3)Symbol.isRegExp** - -对象的Symbol.isRegExp属性,指向一个方法。该对象被用作正则表达式时,会调用这个方法,返回一个布尔值,表示该对象是否为一个正则对象。 - -**(4)Symbol.match** - -对象的Symbol.match属性,指向一个函数。当执行`str.match(myObject)`时,如果该属性存在,会调用它,返回该方法的返回值。 - -**(5)Symbol.iterator** - -对象的Symbol.iterator属性,指向该对象的默认遍历器方法,即该对象进行for...of循环时,会调用这个方法,返回该对象的默认遍历器,详细介绍参见《Iterator和for...of循环》一章。 - -```javascript -class Collection { - *[Symbol.iterator]() { - let i = 0; - while(this[i] !== undefined) { - yield this[i]; - ++i; - } - } - -} - -let myCollection = new Collection(); -myCollection[0] = 1; -myCollection[1] = 2; - -for(let value of myCollection) { - console.log(value); -} -// 1 -// 2 -``` - -**(6)Symbol.toPrimitive** - -对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 - -**(7)Symbol.toStringTag** - -对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用`Object.prototype.toString`方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制`[object Object]`或`[object Array]`中object后面的那个字符串。 - -```javascript -class Collection { - get [Symbol.toStringTag]() { - return 'xxx'; - } -} -var x = new Collection(); -Object.prototype.toString.call(x) // "[object xxx]" -``` - -**(8)Symbol.unscopables** - -对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,那些属性会被with环境排除。 - -```javascript -Array.prototype[Symbol.unscopables] -// { -// copyWithin: true, -// entries: true, -// fill: true, -// find: true, -// findIndex: true, -// keys: true -// } - -Object.keys(Array.prototype[Symbol.unscopables]) -// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'keys'] -``` - -上面代码说明,数组有6个属性,会被with命令排除。 - -```javascript -// 没有unscopables时 -class MyClass { - foo() { return 1; } -} - -var foo = function () { return 2; }; - -with (MyClass.prototype) { - foo(); // 1 -} - -// 有unscopables时 -class MyClass { - foo() { return 1; } - get [Symbol.unscopables]() { - return { foo: true }; - } -} - -var foo = function () { return 2; }; - -with (MyClass.prototype) { - foo(); // 2 -} -``` - ## Proxy ### 概述 diff --git a/docs/reference.md b/docs/reference.md index f9cd35dbd..1177c0a2d 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -19,6 +19,7 @@ - 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新增语法的综合介绍,有很多例子 +- Axel Rauschmayer, [Getting started with ECMAScript 6](http://www.2ality.com/2015/08/getting-started-es6.html): 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/#/) diff --git a/docs/symbol.md b/docs/symbol.md new file mode 100644 index 000000000..b45c5f8e1 --- /dev/null +++ b/docs/symbol.md @@ -0,0 +1,468 @@ +# Symbol + +## 概述 + +ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。 + +ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第八种数据类型,前七种是:数值、字符串、布尔值、数组、对象、函数、undefined。 + +Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 + +```javascript +let s = Symbol(); + +typeof s +// "symbol" +``` + +上面代码中,变量s就是一个独一无二的值。typeof运算符的结果,表明变量s是Symbol数据类型,而不是字符串之类的其他类型。 + +注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。 + +Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。 + +```javascript +var s1 = Symbol('foo'); +var s2 = Symbol('bar'); + +s1 // Symbol(foo) +s2 // Symbol(bar) + +s1.toString() // "Symbol(foo)" +s2.toString() // "Symbol(bar)" +``` + +上面代码中,s1和s2是两个Symbol值。如果不加参数,它们在控制台的输出都是`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值不能与其他类型的值进行运算,会报错。 + +```javascript +var sym = Symbol('My symbol'); + +"your symbol is " + sym +// TypeError: can't convert symbol to string +`your symbol is ${sym}` +// TypeError: can't convert symbol to string +``` + +但是,Symbol值可以转为字符串。 + +```javascript +var sym = Symbol('My symbol'); + +String(sym) // 'Symbol(My symbol)' +sym.toString() // 'Symbol(My symbol)' +``` + +## 作为属性名的Symbol + +由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。 + +```javascript +var mySymbol = Symbol(); + +// 第一种写法 +var a = {}; +a[mySymbol] = 'Hello!'; + +// 第二种写法 +var a = { + [mySymbol]: 'Hello!' +}; + +// 第三种写法 +var a = {}; +Object.defineProperty(a, mySymbol, { value: 'Hello!' }); + +// 以上写法都得到同样结果 +a[mySymbol] // "Hello!" +``` + +上面代码通过方括号结构和Object.defineProperty,将对象的属性名指定为一个Symbol值。 + +注意,Symbol值作为对象属性名时,不能用点运算符。 + +```javascript +var mySymbol = Symbol(); +var a = {}; + +a.mySymbol = 'Hello!'; +a[mySymbol] // undefined +a['mySymbol'] // "Hello!" +``` + +上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个Symbol值。 + +同理,在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。 + +```javascript +let s = Symbol(); + +let obj = { + [s]: function (arg) { ... } +}; + +obj[s](123); +``` + +上面代码中,如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个Symbol值。 + +采用增强的对象写法,上面代码的obj对象可以写得更简洁一些。 + +```javascript +let obj = { + [s](arg) { ... } +}; +``` + +Symbol类型还可以用于定义一组常量,保证这组常量的值都是不相等的。 + +```javascript +log.levels = { + DEBUG: Symbol('debug'), + INFO: Symbol('info'), + WARN: Symbol('warn'), +}; +log(log.levels.DEBUG, 'debug message'); +log(log.levels.INFO, 'info message'); +``` + +还有一点需要注意,Symbol值作为属性名时,该属性还是公开属性,不是私有属性。 + +## 属性名的遍历 + +Symbol作为属性名,该属性不会出现在for...in、for...of循环中,也不会被`Object.keys()`、`Object.getOwnPropertyNames()`返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。 + +Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。 + +```javascript +var obj = {}; +var a = Symbol('a'); +var b = Symbol.for('b'); + +obj[a] = 'Hello'; +obj[b] = 'World'; + +var objectSymbols = Object.getOwnPropertySymbols(obj); + +objectSymbols +// [Symbol(a), Symbol(b)] +``` + +下面是另一个例子,Object.getOwnPropertySymbols方法与for...in循环、Object.getOwnPropertyNames方法进行对比的例子。 + +```javascript +var obj = {}; + +var foo = Symbol("foo"); + +Object.defineProperty(obj, foo, { + value: "foobar", +}); + +for (var i in obj) { + console.log(i); // 无输出 +} + +Object.getOwnPropertyNames(obj) +// [] + +Object.getOwnPropertySymbols(obj) +// [Symbol(foo)] +``` + +上面代码中,使用Object.getOwnPropertyNames方法得不到Symbol属性名,需要使用Object.getOwnPropertySymbols方法。 + +另一个新的API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名。 + +```javascript +let obj = { + [Symbol('my_key')]: 1, + enum: 2, + nonEnum: 3 +}; + +Reflect.ownKeys(obj) +// [Symbol(my_key), 'enum', 'nonEnum'] +``` + +由于以Symbol值作为名称的属性,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。 + +```javascript +var size = Symbol('size'); + +class Collection { + constructor() { + this[size] = 0; + } + + add(item) { + this[this[size]] = item; + this[size]++; + } + + static sizeOf(instance) { + return instance[size]; + } +} + +var x = new Collection(); +Collection.sizeOf(x) // 0 + +x.add('foo'); +Collection.sizeOf(x) // 1 + +Object.keys(x) // ['0'] +Object.getOwnPropertyNames(x) // ['0'] +Object.getOwnPropertySymbols(x) // [Symbol(size)] +``` + +上面代码中,对象x的size属性是一个Symbol值,所以`Object.keys(x)`、`Object.getOwnPropertyNames(x)`都无法获取它。这就造成了一种非私有的内部方法的效果。 + +## Symbol.for(),Symbol.keyFor() + +有时,我们希望重新使用同一个Symbol值,`Symbol.for`方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。 + +```javascript +var s1 = Symbol.for('foo'); +var s2 = Symbol.for('foo'); + +s1 === s2 // true +``` + +上面代码中,s1和s2都是Symbol值,但是它们都是同样参数的`Symbol.for`方法生成的,所以实际上是同一个值。 + +`Symbol.for()`与`Symbol()`这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。`Symbol.for()`不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用`Symbol.for("cat")`30次,每次都会返回同一个Symbol值,但是调用`Symbol("cat")`30次,会返回30个不同的Symbol值。 + +```javascript +Symbol.for("bar") === Symbol.for("bar") +// true + +Symbol("bar") === Symbol("bar") +// false +``` + +上面代码中,由于`Symbol()`写法没有登记机制,所以每次调用都会返回一个不同的值。 + +Symbol.keyFor方法返回一个已登记的Symbol类型值的key。 + +```javascript +var s1 = Symbol.for("foo"); +Symbol.keyFor(s1) // "foo" + +var s2 = Symbol("foo"); +Symbol.keyFor(s2) // undefined +``` + +上面代码中,变量s2属于未登记的Symbol值,所以返回undefined。 + +需要注意的是,`Symbol.for`为Symbol值登记的名字,是全局环境的,可以在不同的iframe或service worker中取到同一个值。 + +```javascript +iframe = document.createElement('iframe'); +iframe.src = String(window.location); +document.body.appendChild(iframe); + +iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo') +// true +``` + +上面代码中,iframe窗口生成的Symbol值,可以在主页面得到。 + +## 内置的Symbol值 + +除了定义自己使用的Symbol值以外,ES6还提供一些内置的Symbol值,指向语言内部使用的方法。 + +### Symbol.hasInstance + +对象的Symbol.hasInstance属性,指向一个内部方法。该对象使用instanceof运算符时,会调用这个方法,判断该对象是否为某个构造函数的实例。比如,`foo instanceof Foo`在语言内部,实际调用的是`Foo[Symbol.hasInstance](foo)`。 + +```javascript +class MyClass { + [Symbol.hasInstance](foo) { + return foo instanceof Array; + } +} +var o = new MyClass(); +o instanceof Array // false +``` + +### Symbol.isConcatSpreadable + +对象的Symbol.isConcatSpreadable属性,指向一个方法。该对象使用Array.prototype.concat()时,会调用这个方法,返回一个布尔值,表示该对象是否可以扩展成数组。 + +```javascript +class A1 extends Array { + [Symbol.isConcatSpreadable]() { + return true; + } +} +class A2 extends Array { + [Symbol.isConcatSpreadable]() { + return false; + } +} +let a1 = new A1(); +a1[0] = 3; +a1[1] = 4; +let a2 = new A2(); +a2[0] = 5; +a2[1] = 6; +[1, 2].concat(a1).concat(a2) +// [1, 2, 3, 4, [5, 6]] +``` + +上面代码中,类A1是可扩展的,类A2是不可扩展的,所以使用concat时有不一样的结果。 + +### Symbol.isRegExp + +对象的Symbol.isRegExp属性,指向一个方法。该对象被用作正则表达式时,会调用这个方法,返回一个布尔值,表示该对象是否为一个正则对象。 + +### Symbol.match + +对象的Symbol.match属性,指向一个函数。当执行`str.match(myObject)`时,如果该属性存在,会调用它,返回该方法的返回值。 + +```javascript +class MyMatcher { + [Symbol.match](string) { + return 'hello world'.indexOf(string); + } +} + +'e'.match(new MyMatcher()) // 1 +``` + +### Symbol.replace + +对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。 + +### Symbol.search + +对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。 + +```javascript +class MySearch { + constructor(value) { + this.value = value; + } + [Symbol.search](string) { + return string.indexOf(this.value); + } +} +'foobar'.search(new MySearch('foo')) // 0 +``` + +### Symbol.split + +对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。 + +### Symbol.iterator + +对象的Symbol.iterator属性,指向该对象的默认遍历器方法,即该对象进行for...of循环时,会调用这个方法,返回该对象的默认遍历器,详细介绍参见《Iterator和for...of循环》一章。 + +```javascript +class Collection { + *[Symbol.iterator]() { + let i = 0; + while(this[i] !== undefined) { + yield this[i]; + ++i; + } + } +} + +let myCollection = new Collection(); +myCollection[0] = 1; +myCollection[1] = 2; + +for(let value of myCollection) { + console.log(value); +} +// 1 +// 2 +``` + +### Symbol.toPrimitive + +对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 + +### Symbol.toStringTag + +对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用`Object.prototype.toString`方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制`[object Object]`或`[object Array]`中object后面的那个字符串。 + +```javascript +class Collection { + get [Symbol.toStringTag]() { + return 'xxx'; + } +} +var x = new Collection(); +Object.prototype.toString.call(x) // "[object xxx]" +``` + +### Symbol.unscopables + +对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。 + +```javascript +Array.prototype[Symbol.unscopables] +// { +// copyWithin: true, +// entries: true, +// fill: true, +// find: true, +// findIndex: true, +// keys: true +// } + +Object.keys(Array.prototype[Symbol.unscopables]) +// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'keys'] +``` + +上面代码说明,数组有6个属性,会被with命令排除。 + +```javascript +// 没有unscopables时 +class MyClass { + foo() { return 1; } +} + +var foo = function () { return 2; }; + +with (MyClass.prototype) { + foo(); // 1 +} + +// 有unscopables时 +class MyClass { + foo() { return 1; } + get [Symbol.unscopables]() { + return { foo: true }; + } +} + +var foo = function () { return 2; }; + +with (MyClass.prototype) { + foo(); // 2 +} +``` + diff --git a/sidebar.md b/sidebar.md index c655486d3..0b5027731 100644 --- a/sidebar.md +++ b/sidebar.md @@ -15,6 +15,7 @@ 1. [数组的扩展](#docs/array) 1. [对象的扩展](#docs/object) 1. [函数的扩展](#docs/function) +1. [Symbol](#docs/symbol) 1. [Set和Map数据结构](#docs/set-map) 1. [Iterator和for...of循环](#docs/iterator) 1. [Generator函数](#docs/generator) From 2ffd5f9e974fb231dd9a611673590e70426f721c Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 9 Aug 2015 17:32:24 +0800 Subject: [PATCH 0025/1267] edit class/decorator --- docs/class.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++ docs/function.md | 20 ++++--------- docs/object.md | 14 +-------- docs/reference.md | 5 ++-- 4 files changed, 82 insertions(+), 29 deletions(-) diff --git a/docs/class.md b/docs/class.md index cddb77531..f435b14f9 100644 --- a/docs/class.md +++ b/docs/class.md @@ -844,6 +844,20 @@ console.log(MyTestableClass.isTestable) // true 上面代码中,`@testable`就是一个修饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。 +基本上,修饰器的行为就是下面这样。 + +```javascript +@decorator +class A {} + +// 等同于 + +class A {} +A = decorator(A) || A; +``` + +也就是说,修饰器本质上就是能在编译时执行的函数。 + 修饰器函数可以接受三个参数,依次是目标函数、属性名和该属性的描述对象。后两个参数可省略。上面代码中,testable函数的参数target,就是所要修饰的对象。如果希望修饰器的行为,能够根据目标对象的不同而不同,就要在外面再封装一层函数。 ```javascript @@ -983,6 +997,64 @@ class Person { 除了注释,修饰器还能用来类型检查。所以,对于Class来说,这项功能相当有用。从长期来看,它将是JavaScript代码静态分析的重要工具。 + +### 为什么修饰器不能用于函数? + +修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。 + +```javascript +var counter = 0; + +var add = function () { + counter++; +}; + +@add +function foo() { +} +``` + +上面的代码,意图是执行后,counter等于1,但是实际上结果是couter等于0。因为函数提升,使得实际执行的代码是下面这样。 + +```javascript +var counter; +var add; + +@add +function foo() { +} + +counter = 0; + +add = function () { + counter++; +}; +``` + +下面是另一个例子。 + +```javascript +var readOnly = require("some-decorator"); + +@readOnly +function foo() { +} +``` + +上面代码也有问题,因为实际执行是下面这样。 + +```javascript +var readOnly; + +@readOnly +function foo() { +} + +readOnly = require("some-decorator"); +``` + +总之,由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。 + ### core-decorators.js [core-decorators.js](https://github.com/jayphelps/core-decorators.js)是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器。 diff --git a/docs/function.md b/docs/function.md index d798dcb44..e830d423d 100644 --- a/docs/function.md +++ b/docs/function.md @@ -150,7 +150,6 @@ foo() 另一个需要注意的地方是,参数默认值所处的作用域,不是全局作用域,而是函数作用域。 ```javascript - var x = 1; function foo(x, y = x) { @@ -158,20 +157,17 @@ function foo(x, y = x) { } foo(2) // 2 - ``` -上面代码中,参数y的默认值等于x,由于处在函数作用域,所以x等于参数x,而不是全局变量x。 +上面代码中,参数y的默认值等于x,由于处在函数作用域,所以y等于参数x,而不是全局变量x。 参数变量是默认声明的,所以不能用let或const再次声明。 ```javascript - function foo(x = 5) { let x = 1; // error const x = 2; // error } - ``` 上面代码中,参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。 @@ -179,7 +175,6 @@ function foo(x = 5) { 参数默认值可以与解构赋值,联合起来使用。 ```javascript - function foo({x, y = 5}) { console.log(x, y); } @@ -187,7 +182,6 @@ function foo({x, y = 5}) { foo({}) // undefined, 5 foo({x: 1}) // 1, 5 foo({x: 1, y: 2}) // 1, 2 - ``` 上面代码中,foo函数的参数是一个对象,变量x和y用于解构赋值,y有默认值5。 @@ -197,19 +191,17 @@ foo({x: 1, y: 2}) // 1, 2 ES6引入rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。 ```javascript - function add(...values) { - let sum = 0; + let sum = 0; - for (var val of values) { - sum += val; - } + for (var val of values) { + sum += val; + } - return sum; + return sum; } add(2, 5, 3) // 10 - ``` 上面代码的add函数是一个求和函数,利用rest参数,可以向该函数传入任意数目的参数。 diff --git a/docs/object.md b/docs/object.md index dc15a382d..a4919a894 100644 --- a/docs/object.md +++ b/docs/object.md @@ -520,7 +520,7 @@ fproxy.foo; // 'Hello, foo' 拦截`for (var x in proxy)`,返回一个遍历器。 -**6)hasOwn(target, propKey)** +**(6)hasOwn(target, propKey)** 拦截`proxy.hasOwnProperty('foo')`,返回一个布尔值。 @@ -842,7 +842,6 @@ Reflect.defineProperty(obj, name, desc); Object.observe方法用来监听对象(以及数组)的变化。一旦监听对象发生变化,就会触发回调函数。 ```javascript - var user = {}; Object.observe(user, function(changes){ changes.forEach(function(change) { @@ -853,7 +852,6 @@ Object.observe(user, function(changes){ user.firstName = 'Michael'; user.lastName = 'Jackson'; user.fullName // 'Michael Jackson' - ``` 上面代码中,Object.observer方法监听user对象。一旦该对象发生变化,就自动生成fullName属性。 @@ -861,7 +859,6 @@ user.fullName // 'Michael Jackson' 一般情况下,Object.observe方法接受两个参数,第一个参数是监听的对象,第二个函数是一个回调函数。一旦监听对象发生变化(比如新增或删除一个属性),就会触发这个回调函数。很明显,利用这个方法可以做很多事情,比如自动更新DOM。 ```javascript - var div = $("#foo"); Object.observe(user, function(changes){ @@ -870,7 +867,6 @@ Object.observe(user, function(changes){ div.text(fullName); }); }); - ``` 上面代码中,只要user对象发生变化,就会自动更新DOM。如果配合jQuery的change方法,就可以实现数据对象与DOM对象的双向自动绑定。 @@ -878,7 +874,6 @@ Object.observe(user, function(changes){ 回调函数的changes参数是一个数组,代表对象发生的变化。下面是一个更完整的例子。 ```javascript - var o = {}; function observer(changes){ @@ -891,20 +886,17 @@ function observer(changes){ } Object.observe(o, observer); - ``` 参照上面代码,Object.observe方法指定的回调函数,接受一个数组(changes)作为参数。该数组的成员与对象的变化一一对应,也就是说,对象发生多少个变化,该数组就有多少个成员。每个成员是一个对象(change),它的name属性表示发生变化源对象的属性名,oldValue属性表示发生变化前的值,object属性指向变动后的源对象,type属性表示变化的种类。基本上,change对象是下面的样子。 ```javascript - var change = { object: {...}, type: 'update', name: 'p2', oldValue: 'Property 2' } - ``` Object.observe方法目前共支持监听六种变化。 @@ -919,9 +911,7 @@ Object.observe方法目前共支持监听六种变化。 Object.observe方法还可以接受第三个参数,用来指定监听的事件种类。 ```javascript - Object.observe(o, observer, ['delete']); - ``` 上面的代码表示,只在发生delete事件时,才会调用回调函数。 @@ -929,9 +919,7 @@ Object.observe(o, observer, ['delete']); Object.unobserve方法用来取消监听。 ```javascript - Object.unobserve(o, observer); - ``` 注意,Object.observe和Object.unobserve这两个方法不属于ES6,而是属于ES7的一部分。不过,Chrome浏览器从33版起就已经支持。 diff --git a/docs/reference.md b/docs/reference.md index 1177c0a2d..47abdda14 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -28,8 +28,8 @@ ## 语法点 -- 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命令的变量声明和赋值的行为 +- 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/) @@ -122,6 +122,7 @@ - Axel Rauschmayer, [Classes in ECMAScript 6 (final semantics)](http://www.2ality.com/2015/02/es6-classes-final.html): Class语法的详细介绍和设计思想分析 - Maximiliano Fierro, [Declarative vs Imperative](http://elmasse.github.io/js/decorators-bindings-es7.html): Decorators和Mixin介绍 - Addy Osmani, [Exploring ES2016 Decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841): Decorator的深入介绍 +- Sebastian McKenzie, [Allow decorators for functions as well](https://github.com/wycats/javascript-decorators/issues/4): 为什么修饰器不能用于函数 - Maximiliano Fierro, [Traits with ES7 Decorators](http://cocktailjs.github.io/blog/traits-with-es7-decorators.html): Trait的用法介绍 ## 模块 From d2ac1f0cc6efc092cb6e8ad1a115e3d4eca4ba86 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 9 Aug 2015 17:41:37 +0800 Subject: [PATCH 0026/1267] =?UTF-8?q?=E5=8A=A0=E5=85=A5docs/decorator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 539 ---------------------------------------------- docs/decorator.md | 539 ++++++++++++++++++++++++++++++++++++++++++++++ sidebar.md | 1 + 3 files changed, 540 insertions(+), 539 deletions(-) create mode 100644 docs/decorator.md diff --git a/docs/class.md b/docs/class.md index f435b14f9..2594c3631 100644 --- a/docs/class.md +++ b/docs/class.md @@ -823,542 +823,3 @@ var y = new Rectangle(3, 4); // 正确 注意,在函数外部,使用`new.target`会报错。 -## 修饰器 - -### 类的修饰 - -修饰器(Decorator)是一个表达式,用来修改类的行为。这是ES7的一个[提案](https://github.com/wycats/javascript-decorators),目前Babel转码器已经支持。 - -修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。 - -```javascript -function testable(target) { - target.isTestable = true; -} - -@testable -class MyTestableClass () {} - -console.log(MyTestableClass.isTestable) // true -``` - -上面代码中,`@testable`就是一个修饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。 - -基本上,修饰器的行为就是下面这样。 - -```javascript -@decorator -class A {} - -// 等同于 - -class A {} -A = decorator(A) || A; -``` - -也就是说,修饰器本质上就是能在编译时执行的函数。 - -修饰器函数可以接受三个参数,依次是目标函数、属性名和该属性的描述对象。后两个参数可省略。上面代码中,testable函数的参数target,就是所要修饰的对象。如果希望修饰器的行为,能够根据目标对象的不同而不同,就要在外面再封装一层函数。 - -```javascript -function testable(isTestable) { - return function(target) { - target.isTestable = isTestable; - } -} - -@testable(true) class MyTestableClass () {} -console.log(MyTestableClass.isTestable) // true - -@testable(false) class MyClass () {} -console.log(MyClass.isTestable) // false -``` - -上面代码中,修饰器testable可以接受参数,这就等于可以修改修饰器的行为。 - -如果想要为类的实例添加方法,可以在修饰器函数中,为目标类的prototype属性添加方法。 - -```javascript -function testable(target) { - target.prototype.isTestable = true; -} - -@testable -class MyTestableClass () {} - -let obj = new MyClass(); - -console.log(obj.isTestable) // true -``` - -上面代码中,修饰器函数testable是在目标类的prototype属性添加属性,因此就可以在类的实例上调用添加的属性。 - -下面是另外一个例子。 - -```javascript -// mixins.js -export function mixins(...list) { - return function (target) { - Object.assign(target.prototype, ...list) - } -} - -// main.js -import { mixins } from './mixins' - -const Foo = { - foo() { console.log('foo') } -} - -@mixins(Foo) -class MyClass {} - -let obj = new MyClass() - -obj.foo() // 'foo' -``` - -上面代码通过修饰器mixins,可以为类添加指定的方法。 - -修饰器可以用`Object.assign()`模拟。 - -```javascript -const Foo = { - foo() { console.log('foo') } -} - -class MyClass {} - -Object.assign(MyClass.prototype, Foo); - -let obj = new MyClass(); -obj.foo() // 'foo' -``` - -### 方法的修饰 - -修饰器不仅可以修饰类,还可以修饰类的属性。 - -```javascript -class Person { - @readonly - name() { return `${this.first} ${this.last}` } -} -``` - -上面代码中,修饰器readonly用来修饰”类“的name方法。 - -此时,修饰器函数一共可以接受三个参数,第一个参数是所要修饰的目标对象,第二个参数是所要修饰的属性名,第三个参数是该属性的描述对象。 - -```javascript -readonly(Person.prototype, 'name', descriptor); - -function readonly(target, name, descriptor){ - // descriptor对象原来的值如下 - // { - // value: specifiedFunction, - // enumerable: false, - // configurable: true, - // writable: true - // }; - descriptor.writable = false; - return descriptor; -} - -Object.defineProperty(Person.prototype, 'name', descriptor); -``` - -上面代码说明,修饰器(readonly)会修改属性的描述对象(descriptor),然后被修改的描述对象再用来定义属性。下面是另一个例子。 - -```javascript -class Person { - @nonenumerable - get kidCount() { return this.children.length; } -} - -function nonenumerable(target, name, descriptor) { - descriptor.enumerable = false; - return descriptor; -} -``` - -修饰器有注释的作用。 - -```javascript -@testable -class Person { - @readonly - @nonenumerable - name() { return `${this.first} ${this.last}` } -} -``` - -从上面代码中,我们一眼就能看出,MyTestableClass类是可测试的,而name方法是只读和不可枚举的。 - -除了注释,修饰器还能用来类型检查。所以,对于Class来说,这项功能相当有用。从长期来看,它将是JavaScript代码静态分析的重要工具。 - - -### 为什么修饰器不能用于函数? - -修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。 - -```javascript -var counter = 0; - -var add = function () { - counter++; -}; - -@add -function foo() { -} -``` - -上面的代码,意图是执行后,counter等于1,但是实际上结果是couter等于0。因为函数提升,使得实际执行的代码是下面这样。 - -```javascript -var counter; -var add; - -@add -function foo() { -} - -counter = 0; - -add = function () { - counter++; -}; -``` - -下面是另一个例子。 - -```javascript -var readOnly = require("some-decorator"); - -@readOnly -function foo() { -} -``` - -上面代码也有问题,因为实际执行是下面这样。 - -```javascript -var readOnly; - -@readOnly -function foo() { -} - -readOnly = require("some-decorator"); -``` - -总之,由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。 - -### core-decorators.js - -[core-decorators.js](https://github.com/jayphelps/core-decorators.js)是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器。 - -**(1)@autobind** - -autobind修饰器使得方法中的this对象,绑定原始对象。 - -```javascript -import { autobind } from 'core-decorators'; - -class Person { - @autobind - getPerson() { - return this; - } -} - -let person = new Person(); -let getPerson = person.getPerson; - -getPerson() === person; -// true -``` - -**(2)@readonly** - -readonly修饰器是的属性或方法不可写。 - -```javascript -import { readonly } from 'core-decorators'; - -class Meal { - @readonly - entree = 'steak'; -} - -var dinner = new Meal(); -dinner.entree = 'salmon'; -// Cannot assign to read only property 'entree' of [object Object] -``` - -**(3)@override** - -override修饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。 - -```javascript -import { override } from 'core-decorators'; - -class Parent { - speak(first, second) {} -} - -class Child extends Parent { - @override - speak() {} - // SyntaxError: Child#speak() does not properly override Parent#speak(first, second) -} - -// or - -class Child extends Parent { - @override - speaks() {} - // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain. - // - // Did you mean "speak"? -} -``` - -**(4)@deprecate (别名@deprecated)** - -deprecate或deprecated修饰器在控制台显示一条警告,表示该方法将废除。 - -```javascript -import { deprecate } from 'core-decorators'; - -class Person { - @deprecate - facepalm() {} - - @deprecate('We stopped facepalming') - facepalmHard() {} - - @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' }) - facepalmHarder() {} -} - -let person = new Person(); - -person.facepalm(); -// DEPRECATION Person#facepalm: This function will be removed in future versions. - -person.facepalmHard(); -// DEPRECATION Person#facepalmHard: We stopped facepalming - -person.facepalmHarder(); -// DEPRECATION Person#facepalmHarder: We stopped facepalming -// -// See http://knowyourmeme.com/memes/facepalm for more details. -// -``` - -**(5)@suppressWarnings** - -suppressWarnings修饰器抑制decorated修饰器导致的`console.warn()`调用。但是,异步代码出发的调用除外。 - -```javascript -import { suppressWarnings } from 'core-decorators'; - -class Person { - @deprecated - facepalm() {} - - @suppressWarnings - facepalmWithoutWarning() { - this.facepalm(); - } -} - -let person = new Person(); - -person.facepalmWithoutWarning(); -// no warning is logged -``` - -### Mixin - -在修饰器的基础上,可以实现Mixin模式。所谓Mixin模式,就是对象继承的一种替代方案,中文译为“混入”(mix in),意为在一个对象之中混入另外一个对象的方法。 - -请看下面的例子。 - -```javascript -const Foo = { - foo() { console.log('foo') } -}; - -class MyClass {} - -Object.assign(MyClass.prototype, Foo); - -let obj = new MyClass(); -obj.foo() // 'foo' -``` - -上面代码之中,对象Foo有一个foo方法,通过`Object.assign`方法,可以将foo方法“混入”MyClass类,导致MyClass的实例obj对象都具有foo方法。这就是“混入”模式的一个简单实现。 - -下面,我们部署一个通用脚本`mixins.js`,将mixin写成一个修饰器。 - -```javascript -export function mixins(...list) { - return function (target) { - Object.assign(target.prototype, ...list); - }; -} -``` - -然后,就可以使用上面这个修饰器,为类“混入”各种方法。 - -```javascript -import { mixins } from './mixins' - -const Foo = { - foo() { console.log('foo') } -}; - -@mixins(Foo) -class MyClass {} - -let obj = new MyClass(); - -obj.foo() // "foo" -``` - -通过mixins这个修饰器,实现了在MyClass类上面“混入”Foo对象的foo方法。 - -### Trait - -Trait也是一种修饰器,功能与Mixin类型,但是提供更多功能,比如防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等等。 - -下面采用[traits-decorator](https://github.com/CocktailJS/traits-decorator)这个第三方模块作为例子。这个模块提供的traits修饰器,不仅可以接受对象,还可以接受ES6类作为参数。 - -```javascript -import {traits } from 'traits-decorator' - -class TFoo { - foo() { console.log('foo') } -} - -const TBar = { - bar() { console.log('bar') } -} - -@traits(TFoo, TBar) -class MyClass { } - -let obj = new MyClass() -obj.foo() // foo -obj.bar() // bar -``` - -上面代码中,通过traits修饰器,在MyClass类上面“混入”了TFoo类的foo方法和TBar对象的bar方法。 - -Trait不允许“混入”同名方法。 - -```javascript -import {traits } from 'traits-decorator' - -class TFoo { - foo() { console.log('foo') } -} - -const TBar = { - bar() { console.log('bar') }, - foo() { console.log('foo') } -} - -@traits(TFoo, TBar) -class MyClass { } -// 报错 -// throw new Error('Method named: ' + methodName + ' is defined twice.'); -// ^ -// Error: Method named: foo is defined twice. -``` - -上面代码中,TFoo和TBar都有foo方法,结果traits修饰器报错。 - -一种解决方法是排除TBar的foo方法。 - -```javascript -import { traits, excludes } from 'traits-decorator' - -class TFoo { - foo() { console.log('foo') } -} - -const TBar = { - bar() { console.log('bar') }, - foo() { console.log('foo') } -} - -@traits(TFoo, TBar::excludes('foo')) -class MyClass { } - -let obj = new MyClass() -obj.foo() // foo -obj.bar() // bar -``` - -上面代码使用绑定运算符(::)在TBar上排除foo方法,混入时就不会报错了。 - -另一种方法是为TBar的foo方法起一个别名。 - -```javascript -import { traits, alias } from 'traits-decorator' - -class TFoo { - foo() { console.log('foo') } -} - -const TBar = { - bar() { console.log('bar') }, - foo() { console.log('foo') } -} - -@traits(TFoo, TBar::alias({foo: 'aliasFoo'})) -class MyClass { } - -let obj = new MyClass() -obj.foo() // foo -obj.aliasFoo() // foo -obj.bar() // bar -``` - -上面代码为TBar的foo方法起了别名aliasFoo,于是MyClass也可以混入TBar的foo方法了。 - -alias和excludes方法,可以结合起来使用。 - -```javascript -@traits(TExample::excludes('foo','bar')::alias({baz:'exampleBaz'})) -class MyClass {} -``` - -上面代码排除了TExample的foo方法和bar方法,为baz方法起了别名exampleBaz。 - -as方法则为上面的代码提供了另一种写法。 - -```javascript -@traits(TExample::as({excludes:['foo', 'bar'], alias: {baz: 'exampleBaz'}})) -class MyClass {} -``` - -### Babel转码器的支持 - -目前,Babel转码器已经支持Decorator,命令行的用法如下。 - -```bash -$ babel --optional es7.decorators -``` - -脚本中打开的命令如下。 - -```javascript -babel.transfrom("code", {optional: ["es7.decorators"]}) -``` - -Babel的官方网站提供一个[在线转码器](https://babeljs.io/repl/),只要勾选Experimental,就能支持Decorator的在线转码。 diff --git a/docs/decorator.md b/docs/decorator.md new file mode 100644 index 000000000..7e1abace0 --- /dev/null +++ b/docs/decorator.md @@ -0,0 +1,539 @@ +# 修饰器 + +## 类的修饰 + +修饰器(Decorator)是一个表达式,用来修改类的行为。这是ES7的一个[提案](https://github.com/wycats/javascript-decorators),目前Babel转码器已经支持。 + +修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。 + +```javascript +function testable(target) { + target.isTestable = true; +} + +@testable +class MyTestableClass () {} + +console.log(MyTestableClass.isTestable) // true +``` + +上面代码中,`@testable`就是一个修饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。 + +基本上,修饰器的行为就是下面这样。 + +```javascript +@decorator +class A {} + +// 等同于 + +class A {} +A = decorator(A) || A; +``` + +也就是说,修饰器本质上就是能在编译时执行的函数。 + +修饰器函数可以接受三个参数,依次是目标函数、属性名和该属性的描述对象。后两个参数可省略。上面代码中,testable函数的参数target,就是所要修饰的对象。如果希望修饰器的行为,能够根据目标对象的不同而不同,就要在外面再封装一层函数。 + +```javascript +function testable(isTestable) { + return function(target) { + target.isTestable = isTestable; + } +} + +@testable(true) class MyTestableClass () {} +console.log(MyTestableClass.isTestable) // true + +@testable(false) class MyClass () {} +console.log(MyClass.isTestable) // false +``` + +上面代码中,修饰器testable可以接受参数,这就等于可以修改修饰器的行为。 + +如果想要为类的实例添加方法,可以在修饰器函数中,为目标类的prototype属性添加方法。 + +```javascript +function testable(target) { + target.prototype.isTestable = true; +} + +@testable +class MyTestableClass () {} + +let obj = new MyClass(); + +console.log(obj.isTestable) // true +``` + +上面代码中,修饰器函数testable是在目标类的prototype属性添加属性,因此就可以在类的实例上调用添加的属性。 + +下面是另外一个例子。 + +```javascript +// mixins.js +export function mixins(...list) { + return function (target) { + Object.assign(target.prototype, ...list) + } +} + +// main.js +import { mixins } from './mixins' + +const Foo = { + foo() { console.log('foo') } +} + +@mixins(Foo) +class MyClass {} + +let obj = new MyClass() + +obj.foo() // 'foo' +``` + +上面代码通过修饰器mixins,可以为类添加指定的方法。 + +修饰器可以用`Object.assign()`模拟。 + +```javascript +const Foo = { + foo() { console.log('foo') } +} + +class MyClass {} + +Object.assign(MyClass.prototype, Foo); + +let obj = new MyClass(); +obj.foo() // 'foo' +``` + +## 方法的修饰 + +修饰器不仅可以修饰类,还可以修饰类的属性。 + +```javascript +class Person { + @readonly + name() { return `${this.first} ${this.last}` } +} +``` + +上面代码中,修饰器readonly用来修饰”类“的name方法。 + +此时,修饰器函数一共可以接受三个参数,第一个参数是所要修饰的目标对象,第二个参数是所要修饰的属性名,第三个参数是该属性的描述对象。 + +```javascript +readonly(Person.prototype, 'name', descriptor); + +function readonly(target, name, descriptor){ + // descriptor对象原来的值如下 + // { + // value: specifiedFunction, + // enumerable: false, + // configurable: true, + // writable: true + // }; + descriptor.writable = false; + return descriptor; +} + +Object.defineProperty(Person.prototype, 'name', descriptor); +``` + +上面代码说明,修饰器(readonly)会修改属性的描述对象(descriptor),然后被修改的描述对象再用来定义属性。下面是另一个例子。 + +```javascript +class Person { + @nonenumerable + get kidCount() { return this.children.length; } +} + +function nonenumerable(target, name, descriptor) { + descriptor.enumerable = false; + return descriptor; +} +``` + +修饰器有注释的作用。 + +```javascript +@testable +class Person { + @readonly + @nonenumerable + name() { return `${this.first} ${this.last}` } +} +``` + +从上面代码中,我们一眼就能看出,MyTestableClass类是可测试的,而name方法是只读和不可枚举的。 + +除了注释,修饰器还能用来类型检查。所以,对于Class来说,这项功能相当有用。从长期来看,它将是JavaScript代码静态分析的重要工具。 + + +## 为什么修饰器不能用于函数? + +修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。 + +```javascript +var counter = 0; + +var add = function () { + counter++; +}; + +@add +function foo() { +} +``` + +上面的代码,意图是执行后,counter等于1,但是实际上结果是couter等于0。因为函数提升,使得实际执行的代码是下面这样。 + +```javascript +var counter; +var add; + +@add +function foo() { +} + +counter = 0; + +add = function () { + counter++; +}; +``` + +下面是另一个例子。 + +```javascript +var readOnly = require("some-decorator"); + +@readOnly +function foo() { +} +``` + +上面代码也有问题,因为实际执行是下面这样。 + +```javascript +var readOnly; + +@readOnly +function foo() { +} + +readOnly = require("some-decorator"); +``` + +总之,由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。 + +## core-decorators.js + +[core-decorators.js](https://github.com/jayphelps/core-decorators.js)是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器。 + +**(1)@autobind** + +autobind修饰器使得方法中的this对象,绑定原始对象。 + +```javascript +import { autobind } from 'core-decorators'; + +class Person { + @autobind + getPerson() { + return this; + } +} + +let person = new Person(); +let getPerson = person.getPerson; + +getPerson() === person; +// true +``` + +**(2)@readonly** + +readonly修饰器是的属性或方法不可写。 + +```javascript +import { readonly } from 'core-decorators'; + +class Meal { + @readonly + entree = 'steak'; +} + +var dinner = new Meal(); +dinner.entree = 'salmon'; +// Cannot assign to read only property 'entree' of [object Object] +``` + +**(3)@override** + +override修饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。 + +```javascript +import { override } from 'core-decorators'; + +class Parent { + speak(first, second) {} +} + +class Child extends Parent { + @override + speak() {} + // SyntaxError: Child#speak() does not properly override Parent#speak(first, second) +} + +// or + +class Child extends Parent { + @override + speaks() {} + // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain. + // + // Did you mean "speak"? +} +``` + +**(4)@deprecate (别名@deprecated)** + +deprecate或deprecated修饰器在控制台显示一条警告,表示该方法将废除。 + +```javascript +import { deprecate } from 'core-decorators'; + +class Person { + @deprecate + facepalm() {} + + @deprecate('We stopped facepalming') + facepalmHard() {} + + @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' }) + facepalmHarder() {} +} + +let person = new Person(); + +person.facepalm(); +// DEPRECATION Person#facepalm: This function will be removed in future versions. + +person.facepalmHard(); +// DEPRECATION Person#facepalmHard: We stopped facepalming + +person.facepalmHarder(); +// DEPRECATION Person#facepalmHarder: We stopped facepalming +// +// See http://knowyourmeme.com/memes/facepalm for more details. +// +``` + +**(5)@suppressWarnings** + +suppressWarnings修饰器抑制decorated修饰器导致的`console.warn()`调用。但是,异步代码出发的调用除外。 + +```javascript +import { suppressWarnings } from 'core-decorators'; + +class Person { + @deprecated + facepalm() {} + + @suppressWarnings + facepalmWithoutWarning() { + this.facepalm(); + } +} + +let person = new Person(); + +person.facepalmWithoutWarning(); +// no warning is logged +``` + +## Mixin + +在修饰器的基础上,可以实现Mixin模式。所谓Mixin模式,就是对象继承的一种替代方案,中文译为“混入”(mix in),意为在一个对象之中混入另外一个对象的方法。 + +请看下面的例子。 + +```javascript +const Foo = { + foo() { console.log('foo') } +}; + +class MyClass {} + +Object.assign(MyClass.prototype, Foo); + +let obj = new MyClass(); +obj.foo() // 'foo' +``` + +上面代码之中,对象Foo有一个foo方法,通过`Object.assign`方法,可以将foo方法“混入”MyClass类,导致MyClass的实例obj对象都具有foo方法。这就是“混入”模式的一个简单实现。 + +下面,我们部署一个通用脚本`mixins.js`,将mixin写成一个修饰器。 + +```javascript +export function mixins(...list) { + return function (target) { + Object.assign(target.prototype, ...list); + }; +} +``` + +然后,就可以使用上面这个修饰器,为类“混入”各种方法。 + +```javascript +import { mixins } from './mixins' + +const Foo = { + foo() { console.log('foo') } +}; + +@mixins(Foo) +class MyClass {} + +let obj = new MyClass(); + +obj.foo() // "foo" +``` + +通过mixins这个修饰器,实现了在MyClass类上面“混入”Foo对象的foo方法。 + +## Trait + +Trait也是一种修饰器,功能与Mixin类型,但是提供更多功能,比如防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等等。 + +下面采用[traits-decorator](https://github.com/CocktailJS/traits-decorator)这个第三方模块作为例子。这个模块提供的traits修饰器,不仅可以接受对象,还可以接受ES6类作为参数。 + +```javascript +import {traits } from 'traits-decorator' + +class TFoo { + foo() { console.log('foo') } +} + +const TBar = { + bar() { console.log('bar') } +} + +@traits(TFoo, TBar) +class MyClass { } + +let obj = new MyClass() +obj.foo() // foo +obj.bar() // bar +``` + +上面代码中,通过traits修饰器,在MyClass类上面“混入”了TFoo类的foo方法和TBar对象的bar方法。 + +Trait不允许“混入”同名方法。 + +```javascript +import {traits } from 'traits-decorator' + +class TFoo { + foo() { console.log('foo') } +} + +const TBar = { + bar() { console.log('bar') }, + foo() { console.log('foo') } +} + +@traits(TFoo, TBar) +class MyClass { } +// 报错 +// throw new Error('Method named: ' + methodName + ' is defined twice.'); +// ^ +// Error: Method named: foo is defined twice. +``` + +上面代码中,TFoo和TBar都有foo方法,结果traits修饰器报错。 + +一种解决方法是排除TBar的foo方法。 + +```javascript +import { traits, excludes } from 'traits-decorator' + +class TFoo { + foo() { console.log('foo') } +} + +const TBar = { + bar() { console.log('bar') }, + foo() { console.log('foo') } +} + +@traits(TFoo, TBar::excludes('foo')) +class MyClass { } + +let obj = new MyClass() +obj.foo() // foo +obj.bar() // bar +``` + +上面代码使用绑定运算符(::)在TBar上排除foo方法,混入时就不会报错了。 + +另一种方法是为TBar的foo方法起一个别名。 + +```javascript +import { traits, alias } from 'traits-decorator' + +class TFoo { + foo() { console.log('foo') } +} + +const TBar = { + bar() { console.log('bar') }, + foo() { console.log('foo') } +} + +@traits(TFoo, TBar::alias({foo: 'aliasFoo'})) +class MyClass { } + +let obj = new MyClass() +obj.foo() // foo +obj.aliasFoo() // foo +obj.bar() // bar +``` + +上面代码为TBar的foo方法起了别名aliasFoo,于是MyClass也可以混入TBar的foo方法了。 + +alias和excludes方法,可以结合起来使用。 + +```javascript +@traits(TExample::excludes('foo','bar')::alias({baz:'exampleBaz'})) +class MyClass {} +``` + +上面代码排除了TExample的foo方法和bar方法,为baz方法起了别名exampleBaz。 + +as方法则为上面的代码提供了另一种写法。 + +```javascript +@traits(TExample::as({excludes:['foo', 'bar'], alias: {baz: 'exampleBaz'}})) +class MyClass {} +``` + +### Babel转码器的支持 + +目前,Babel转码器已经支持Decorator,命令行的用法如下。 + +```bash +$ babel --optional es7.decorators +``` + +脚本中打开的命令如下。 + +```javascript +babel.transfrom("code", {optional: ["es7.decorators"]}) +``` + +Babel的官方网站提供一个[在线转码器](https://babeljs.io/repl/),只要勾选Experimental,就能支持Decorator的在线转码。 diff --git a/sidebar.md b/sidebar.md index 0b5027731..9c72668a0 100644 --- a/sidebar.md +++ b/sidebar.md @@ -22,6 +22,7 @@ 1. [Promise对象](#docs/promise) 1. [异步操作](#docs/async) 1. [Class](#docs/class) +1. [Decorator](#docs/decorator) 1. [Module](#docs/module) 1. [编程风格](#docs/style) 1. [参考链接](#docs/reference) From 77f16ab5f781a5d8a1403a28503eaa02aa7cf134 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 12 Aug 2015 08:10:34 +0800 Subject: [PATCH 0027/1267] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20class=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20proxy=E5=92=8Creflect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 171 ++++++++++++++++- docs/object.md | 466 ++-------------------------------------------- docs/proxy.md | 443 +++++++++++++++++++++++++++++++++++++++++++ docs/reference.md | 8 +- sidebar.md | 1 + 5 files changed, 626 insertions(+), 463 deletions(-) create mode 100644 docs/proxy.md diff --git a/docs/class.md b/docs/class.md index 2594c3631..b644ffc2a 100644 --- a/docs/class.md +++ b/docs/class.md @@ -436,7 +436,44 @@ B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true ``` -上面代码中,子类B的`__proto__`属性指向父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性。 +上面代码中,子类B的`__proto__`属性指向父类A,子类B的prototype属性的`__proto__`属性指向父类A的prototype属性。 + +这样的结果是因为,类的继承是按照下面的模式实现的。 + +```javascript +class A { +} + +class B { +} + +// B的实例继承A的实例 +Object.setPrototypeOf(B.prototype, A.prototype); + +// B继承A的静态属性 +Object.setPrototypeOf(B, A); +``` + +《对象的扩展》一章给出过`Object.setPrototypeOf`方法的实现。 + +``` +Object.setPrototypeOf = function (obj, proto) { + obj.__proto__ = proto; + return obj; +} +``` + +因此,就得到了上面的结果。 + +```javascript +Object.setPrototypeOf(B.prototype, A.prototype); +// 等同于 +B.prototype.__proto__ = A.prototype; + +Object.setPrototypeOf(B, A); +// 等同于 +B.__proto__ = A; +``` 这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(`__proto__`属性)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。 @@ -446,7 +483,20 @@ B.prototype = new A(); B.prototype.__proto__ = A.prototype; ``` -此外,考虑三种特殊情况。第一种特殊情况,子类继承Object类。 +### Extends 的继承目标 + +extends关键字后面可以跟多种类型的值。 + +```javascript +class B extends A { +} +``` + +上面代码的A,只要是一个有prototype属性的函数,就能被B继承。由于函数都有prototype属性,因此A可以是任意函数。 + +下面,讨论三种特殊情况。 + +第一种特殊情况,子类继承Object类。 ```javascript class A extends Object { @@ -497,6 +547,37 @@ Object.getPrototypeOf(ColorPoint) === Point // true ``` +因此,可以使用这个方法判断,一个类是否继承了另一个类。 + +### super关键字 + +上面讲过,在子类中,super关键字代表父类实例。 + +```javascript +class B extends A { + get m() { + return this._p * super._p; + } + set m() { + throw new Error('该属性只读'); + } +} +``` + +上面代码中,子类通过super关键字,调用父类的实例。 + +由于,对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字。 + +```javascript +var obj = { + toString() { + return "MyObject: " + super.toString(); + } +} + +obj.toString(); // MyObject: [object Object] +``` + ### 实例的\_\_proto\_\_属性 子类实例的\_\_proto\_\_属性的\_\_proto\_\_属性,指向父类实例的\_\_proto\_\_属性。也就是说,子类的原型的原型,是父类的原型。 @@ -509,7 +590,9 @@ p2.__proto__ === p1.__proto // false p2.__proto__.__proto__ === p1.__proto__ // true ``` -通过子类实例的\_\_proto\_\_属性,可以修改父类实例的行为。 +上面代码中,ColorPoint继承了Point,导致前者原型的原型是后者的原型。 + +因此,通过子类实例的`__proto__.__proto__`属性,可以修改父类实例的行为。 ```javascript p2.__proto__.__proto__.printName = function () { @@ -521,7 +604,7 @@ p1.printName() // "Ha" 上面代码在ColorPoint的实例p2上向Point类添加方法,结果影响到了Point的实例p1。 -### 原生构造函数的继承 +## 原生构造函数的继承 原生构造函数是指语言内置的构造函数,通常用来生成数据结构,比如`Array()`。以前,这些原生构造函数是无法继承的,即不能自己定义一个Array的子类。 @@ -572,13 +655,51 @@ arr[0] // undefined 上面代码定义了一个MyArray类,继承了Array构造函数,因此就可以从MyArray生成数组的实例。这意味着,ES6可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。 -上面这个例子也说明,extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。下面是一个自定义Error子类的例子。 +上面这个例子也说明,extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构。下面就是定义了一个带版本功能的数组。 + +```javascript +class VersionedArray extends Array { + constructor() { + super(); + this.history = [[]]; + } + commit() { + this.history.push(this.slice()); + } + revert() { + this.splice(0, this.length, this.history[this.history.length - 1]); + } +} +``` + +上面代码中,VersionedArray结构会通过commit方法,将自己的上一个版本存入history属性,然后通过revert方法,可以撤销当前版本,回到上一个版本。除此之外,VersionedArray依然是一个数组,所有原生的数组方法都可以在它上面调用。 + +下面是一个自定义Error子类的例子。 ```javascript -class MyError extends Error { +class ExtendableError extends Error { + constructor(message) { + super(); + this.message = message; + this.stack = (new Error()).stack; + this.name = this.constructor.name; + } } -throw new MyError('Something happened!'); +class MyError extends ExtendableError { + constructor(m) { + super(m); + } +} + +var myerror = new MyError('ll'); +myerror.message // "ll" +myerror instanceof Error // true +myerror.name // "MyError" +myerror.stack +// Error +// at MyError.ExtendableError +// ... ``` ## class的取值函数(getter)和存值函数(setter) @@ -823,3 +944,39 @@ var y = new Rectangle(3, 4); // 正确 注意,在函数外部,使用`new.target`会报错。 +## Mixin模式的实现 + +Mixin模式指的是,将多个类的接口“混入”(mix in)另一个类。它在ES6的实现如下。 + +```javascript +function mix(...mixins) { + class Mix {} + + for (let mixin of mixins) { + copyProperties(Mix, mixin); + copyProperties(Mix.prototype, mixin.prototype); + } + + return Mix; +} + +function copyProperties(target, source) { + for (let key of Reflect.ownKeys(source)) { + if ( key !== "constructor" + && key !== "prototype" + && key !== "name" + ) { + let desc = Object.getOwnPropertyDescriptor(source, key); + Object.defineProperty(target, key, desc); + } + } +} +``` + +上面代码的mix函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可。 + +```javascript +class DistributedEdit extends mix(Loggable, Serializable) { + // ... +} +``` diff --git a/docs/object.md b/docs/object.md index a4919a894..7062fa9ca 100644 --- a/docs/object.md +++ b/docs/object.md @@ -93,24 +93,24 @@ ES6允许字面量定义对象时,用方法二(表达式)作为对象的 let propKey = 'foo'; let obj = { - [propKey]: true, - ['a'+'bc']: 123 + [propKey]: true, + ['a'+'bc']: 123 }; ``` 下面是另一个例子。 ```javascript -var lastWord = "last word"; +var lastWord = 'last word'; var a = { - "first word": "hello", - [lastWord]: "world" + 'first word': 'hello', + [lastWord]: 'world' }; -a["first word"] // "hello" +a['first word'] // "hello" a[lastWord] // "world" -a["last word"] // "world" +a['last word'] // "world" ``` 表达式还可以用于定义方法名。 @@ -122,7 +122,7 @@ let obj = { } }; -console.log(obj.hello()); // hi +obj.hello() // hi ``` ## 方法的name属性 @@ -150,9 +150,11 @@ var doSomething = function() { // ... }; -console.log(doSomething.bind().name); // "bound doSomething" +doSomething.bind().name +// "bound doSomething" -console.log((new Function()).name); // "anonymous" +(new Function()).name +// "anonymous" ``` 有两种特殊情况:bind方法创造的函数,name属性返回“bound”加上原函数的名字;Function构造函数创造的函数,name属性返回“anonymous”。 @@ -393,450 +395,6 @@ Object.getPrototypeOf(rec) === Rectangle.prototype // false ``` -## Proxy - -### 概述 - -Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。 - -Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。 - -```javascript -var obj = new Proxy({}, { - get: function (target, key, receiver) { - console.log(`getting ${key}!`); - return Reflect.get(target, key, receiver); - }, - set: function (target, key, value, receiver) { - console.log(`setting ${key}!`); - return Reflect.set(target, key, value, receiver); - } -}); -``` - -上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。 - -```javascript -obj.count = 1 -// setting count! -++obj.count -// getting count! -// setting count! -// 2 -``` - -上面代码说明,Proxy实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。 - -ES6原生提供Proxy构造函数,用来生成Proxy实例。 - -```javascript -var proxy = new Proxy(target, handler) -``` - -Proxy对象的所用用法,都是上面这种形式,不同的只是handler参数的写法。其中,`new Proxy()`表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。 - -下面是另一个拦截读取属性行为的例子。 - -```javascript -var proxy = new Proxy({}, { - get: function(target, property) { - return 35; - } -}); - -proxy.time // 35 -proxy.name // 35 -proxy.title // 35 -``` - -上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。 - -注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。 - -一个技巧是将Proxy对象,设置到`object.proxy`属性,从而可以在object对象上调用。 - -```javascript -var object = { proxy: new Proxy(target, handler) } -``` - -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 handler = { - get: function(target, name) { - if (name === 'prototype') return Object.prototype; - return 'Hello, '+ name; - }, - apply: function(target, thisBinding, args) { return args[0]; }, - construct: function(target, args) { return args[1]; } -}; - -var fproxy = new Proxy(function(x,y) { - return x+y; -}, handler); - -fproxy(1,2); // 1 -new fproxy(1,2); // 2 -fproxy.prototype; // Object.prototype -fproxy.foo; // 'Hello, foo' -``` - -下面是Proxy支持的拦截操作一览。 - -对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。 - -**(1)get(target, propKey, receiver)** - -拦截对象属性的读取,比如`proxy.foo`和`proxy['foo']`,返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。 - -**(2)set(target, propKey, value, receiver)** - -拦截对象属性的设置,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一个布尔值。 - -**(3)has(target, propKey)** - -拦截`propKey in proxy`的操作,返回一个布尔值。 - -**(4)deleteProperty(target, propKey)** - -拦截`delete proxy[propKey]`的操作,返回一个布尔值。 - -**(5)enumerate(target)** - -拦截`for (var x in proxy)`,返回一个遍历器。 - -**(6)hasOwn(target, propKey)** - -拦截`proxy.hasOwnProperty('foo')`,返回一个布尔值。 - -**(7)ownKeys(target)** - -拦截`Object.getOwnPropertyNames(proxy)`、`Object.getOwnPropertySymbols(proxy)`、`Object.keys(proxy)`,返回一个数组。该方法返回对象所有自身的属性,而`Object.keys()`仅返回对象可遍历的属性。 - -**(8)getOwnPropertyDescriptor(target, propKey)** - -拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象。 - -**(9)defineProperty(target, propKey, propDesc)** - -拦截`Object.defineProperty(proxy, propKey, propDesc)`、`Object.defineProperties(proxy, propDescs)`,返回一个布尔值。 - -**(10)preventExtensions(target)** - -拦截`Object.preventExtensions(proxy)`,返回一个布尔值。 - -**(11)getPrototypeOf(target)** - -拦截`Object.getPrototypeOf(proxy)`,返回一个对象。 - -**(12)isExtensible(target)** - -拦截`Object.isExtensible(proxy)`,返回一个布尔值。 - -**(13)setPrototypeOf(target, proto)** - -拦截`Object.setPrototypeOf(proxy, proto)`,返回一个布尔值。 - -如果目标对象是函数,那么还有两种额外操作可以拦截。 - -**(14)apply(target, object, args)** - -拦截Proxy实例作为函数调用的操作,比如`proxy(...args)`、`proxy.call(object, ...args)`、`proxy.apply(...)`。 - -**(15)construct(target, args, proxy)** - -拦截Proxy实例作为构造函数调用的操作,比如new proxy(...args)。 - -下面是其中几个重要拦截方法的详细介绍。 - -### get() - -get方法用于拦截某个属性的读取操作。上文已经有一个例子,下面是另一个拦截读取操作的例子。 - -```javascript -var person = { - 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."); - } - } -}); - -proxy.name // "张三" -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以后,达到了将函数名链式使用的效果。 - -### set() - -set方法用来拦截某个属性的赋值操作。假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性值符合要求。 - -```javascript - -let validator = { - set: function(obj, prop, value) { - if (prop === 'age') { - if (!Number.isInteger(value)) { - throw new TypeError('The age is not an integer'); - } - if (value > 200) { - throw new RangeError('The age seems invalid'); - } - } - - // 对于age以外的属性,直接保存 - obj[prop] = value; - } -}; - -let person = new Proxy({}, validator); - -person.age = 100; - -person.age // 100 -person.age = 'young' // 报错 -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 -let target = {}; - -let handler = { - ownKeys(target) { - return ['hello', 'world']; - } -}; - -let proxy = new Proxy(target, handler); - -Object.keys(proxy) -// [ 'hello', 'world' ] -``` - -上面代码拦截了对于target对象的Object.keys()操作,返回预先设定的数组。 - -### Proxy.revocable() - -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实例,就会抛出一个错误。 - -## Reflect - -### 概述 - -Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计目的有这样几个。 - -(1) 将Object对象的一些明显属于语言层面的方法,放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。 - -(2) 修改某些Object方法的返回结果,让其变得更合理。比如,`Object.defineProperty(obj, name, desc)`在无法定义属性时,会抛出一个错误,而`Reflect.defineProperty(obj, name, desc)`则会返回false。 - -(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如`name in obj`和`delete obj[name]`,而`Reflect.has(obj, name)`和`Reflect.deleteProperty(obj, name)`让它们变成了函数行为。 - -(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。 - -```javascript -Proxy(target, { - set: function(target, name, value, receiver) { - var success = Reflect.set(target,name, value, receiver); - if (success) { - log('property '+name+' on '+target+' set to '+value); - } - return success; - } -}); -``` - -上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,然后再部署额外的功能。 - -下面是get方法的例子。 - -```javascript -var loggedObj = new Proxy(obj, { - get: function(target, name) { - console.log("get", target, name); - return Reflect.get(target, name); - } -}); -``` - -### 方法 - -Reflect对象的方法清单如下。 - -- Reflect.getOwnPropertyDescriptor(target,name) -- Reflect.defineProperty(target,name,desc) -- Reflect.getOwnPropertyNames(target) -- Reflect.getPrototypeOf(target) -- Reflect.deleteProperty(target,name) -- Reflect.enumerate(target) -- Reflect.freeze(target) -- Reflect.seal(target) -- Reflect.preventExtensions(target) -- Reflect.isFrozen(target) -- Reflect.isSealed(target) -- Reflect.isExtensible(target) -- Reflect.has(target,name) -- Reflect.hasOwn(target,name) -- Reflect.keys(target) -- Reflect.get(target,name,receiver) -- Reflect.set(target,name,value,receiver) -- Reflect.apply(target,thisArg,args) -- Reflect.construct(target,args) - -上面这些方法的作用,大部分与Object对象的同名方法的作用都是相同的。下面是对其中几个方法的解释。 - -(1)Reflect.get(target,name,receiver) - -查找并返回target对象的name属性,如果没有该属性,则返回undefined。 - -如果name属性部署了读取函数,则读取函数的this绑定receiver。 - -```javascript -var obj = { - get foo() { return this.bar(); }, - bar: function() { ... } -} - -// 下面语句会让 this.bar() -// 变成调用 wrapper.bar() -Reflect.get(obj, "foo", wrapper); -``` - -(2)Reflect.set(target, name, value, receiver) - -设置target对象的name属性等于value。如果name属性设置了赋值函数,则赋值函数的this绑定receiver。 - -(3)Reflect.has(obj, name) - -等同于`name in obj`。 - -(4)Reflect.deleteProperty(obj, name) - -等同于`delete obj[name]`。 - -(5)Reflect.construct(target, args) - -等同于`new target(...args)`,这提供了一种不使用new,来调用构造函数的方法。 - -(6)Reflect.getPrototypeOf(obj) - -读取对象的\_\_proto\_\_属性,等同于`Object.getPrototypeOf(obj)`。 - -(7)Reflect.setPrototypeOf(obj, newProto) - -设置对象的\_\_proto\_\_属性。注意,Object对象没有对应这个方法的方法。 - -(8)Reflect.apply(fun,thisArg,args) - -等同于`Function.prototype.apply.call(fun,thisArg,args)`。一般来说,如果要绑定一个函数的this对象,可以这样写`fn.apply(obj, args)`,但是如果函数定义了自己的apply方法,就只能写成`Function.prototype.apply.call(fn, obj, args)`,采用Reflect对象可以简化这种操作。 - -另外,需要注意的是,Reflect.set()、Reflect.defineProperty()、Reflect.freeze()、Reflect.seal()和Reflect.preventExtensions()返回一个布尔值,表示操作是否成功。它们对应的Object方法,失败时都会抛出错误。 - -```javascript -// 失败时抛出错误 -Object.defineProperty(obj, name, desc); -// 失败时返回false -Reflect.defineProperty(obj, name, desc); -``` - -上面代码中,Reflect.defineProperty方法的作用与Object.defineProperty是一样的,都是为对象定义一个属性。但是,Reflect.defineProperty方法失败时,不会抛出错误,只会返回false。 - ## Object.observe(),Object.unobserve() Object.observe方法用来监听对象(以及数组)的变化。一旦监听对象发生变化,就会触发回调函数。 diff --git a/docs/proxy.md b/docs/proxy.md new file mode 100644 index 000000000..862932e8d --- /dev/null +++ b/docs/proxy.md @@ -0,0 +1,443 @@ +# Proxy和Reflect + +## Proxy概述 + +Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。 + +Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。 + +```javascript +var obj = new Proxy({}, { + get: function (target, key, receiver) { + console.log(`getting ${key}!`); + return Reflect.get(target, key, receiver); + }, + set: function (target, key, value, receiver) { + console.log(`setting ${key}!`); + return Reflect.set(target, key, value, receiver); + } +}); +``` + +上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。 + +```javascript +obj.count = 1 +// setting count! +++obj.count +// getting count! +// setting count! +// 2 +``` + +上面代码说明,Proxy实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。 + +ES6原生提供Proxy构造函数,用来生成Proxy实例。 + +```javascript +var proxy = new Proxy(target, handler) +``` + +Proxy对象的所用用法,都是上面这种形式,不同的只是handler参数的写法。其中,`new Proxy()`表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。 + +下面是另一个拦截读取属性行为的例子。 + +```javascript +var proxy = new Proxy({}, { + get: function(target, property) { + return 35; + } +}); + +proxy.time // 35 +proxy.name // 35 +proxy.title // 35 +``` + +上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。 + +注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。 + +一个技巧是将Proxy对象,设置到`object.proxy`属性,从而可以在object对象上调用。 + +```javascript +var object = { proxy: new Proxy(target, handler) } +``` + +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 handler = { + get: function(target, name) { + if (name === 'prototype') return Object.prototype; + return 'Hello, '+ name; + }, + apply: function(target, thisBinding, args) { return args[0]; }, + construct: function(target, args) { return args[1]; } +}; + +var fproxy = new Proxy(function(x,y) { + return x+y; +}, handler); + +fproxy(1,2); // 1 +new fproxy(1,2); // 2 +fproxy.prototype; // Object.prototype +fproxy.foo; // 'Hello, foo' +``` + +下面是Proxy支持的拦截操作一览。 + +对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。 + +**(1)get(target, propKey, receiver)** + +拦截对象属性的读取,比如`proxy.foo`和`proxy['foo']`,返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。 + +**(2)set(target, propKey, value, receiver)** + +拦截对象属性的设置,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一个布尔值。 + +**(3)has(target, propKey)** + +拦截`propKey in proxy`的操作,返回一个布尔值。 + +**(4)deleteProperty(target, propKey)** + +拦截`delete proxy[propKey]`的操作,返回一个布尔值。 + +**(5)enumerate(target)** + +拦截`for (var x in proxy)`,返回一个遍历器。 + +**(6)hasOwn(target, propKey)** + +拦截`proxy.hasOwnProperty('foo')`,返回一个布尔值。 + +**(7)ownKeys(target)** + +拦截`Object.getOwnPropertyNames(proxy)`、`Object.getOwnPropertySymbols(proxy)`、`Object.keys(proxy)`,返回一个数组。该方法返回对象所有自身的属性,而`Object.keys()`仅返回对象可遍历的属性。 + +**(8)getOwnPropertyDescriptor(target, propKey)** + +拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象。 + +**(9)defineProperty(target, propKey, propDesc)** + +拦截`Object.defineProperty(proxy, propKey, propDesc)`、`Object.defineProperties(proxy, propDescs)`,返回一个布尔值。 + +**(10)preventExtensions(target)** + +拦截`Object.preventExtensions(proxy)`,返回一个布尔值。 + +**(11)getPrototypeOf(target)** + +拦截`Object.getPrototypeOf(proxy)`,返回一个对象。 + +**(12)isExtensible(target)** + +拦截`Object.isExtensible(proxy)`,返回一个布尔值。 + +**(13)setPrototypeOf(target, proto)** + +拦截`Object.setPrototypeOf(proxy, proto)`,返回一个布尔值。 + +如果目标对象是函数,那么还有两种额外操作可以拦截。 + +**(14)apply(target, object, args)** + +拦截Proxy实例作为函数调用的操作,比如`proxy(...args)`、`proxy.call(object, ...args)`、`proxy.apply(...)`。 + +**(15)construct(target, args, proxy)** + +拦截Proxy实例作为构造函数调用的操作,比如new proxy(...args)。 + +## Proxy实例的方法 + +下面是其中几个重要拦截方法的详细介绍。 + +### get() + +get方法用于拦截某个属性的读取操作。上文已经有一个例子,下面是另一个拦截读取操作的例子。 + +```javascript +var person = { + 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."); + } + } +}); + +proxy.name // "张三" +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以后,达到了将函数名链式使用的效果。 + +### set() + +set方法用来拦截某个属性的赋值操作。假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性值符合要求。 + +```javascript + +let validator = { + set: function(obj, prop, value) { + if (prop === 'age') { + if (!Number.isInteger(value)) { + throw new TypeError('The age is not an integer'); + } + if (value > 200) { + throw new RangeError('The age seems invalid'); + } + } + + // 对于age以外的属性,直接保存 + obj[prop] = value; + } +}; + +let person = new Proxy({}, validator); + +person.age = 100; + +person.age // 100 +person.age = 'young' // 报错 +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 +let target = {}; + +let handler = { + ownKeys(target) { + return ['hello', 'world']; + } +}; + +let proxy = new Proxy(target, handler); + +Object.keys(proxy) +// [ 'hello', 'world' ] +``` + +上面代码拦截了对于target对象的Object.keys()操作,返回预先设定的数组。 + +## Proxy.revocable() + +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实例,就会抛出一个错误。 + +## Reflect概述 + +Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计目的有这样几个。 + +(1) 将Object对象的一些明显属于语言层面的方法,放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。 + +(2) 修改某些Object方法的返回结果,让其变得更合理。比如,`Object.defineProperty(obj, name, desc)`在无法定义属性时,会抛出一个错误,而`Reflect.defineProperty(obj, name, desc)`则会返回false。 + +(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如`name in obj`和`delete obj[name]`,而`Reflect.has(obj, name)`和`Reflect.deleteProperty(obj, name)`让它们变成了函数行为。 + +(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。 + +```javascript +Proxy(target, { + set: function(target, name, value, receiver) { + var success = Reflect.set(target,name, value, receiver); + if (success) { + log('property '+name+' on '+target+' set to '+value); + } + return success; + } +}); +``` + +上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,然后再部署额外的功能。 + +下面是get方法的例子。 + +```javascript +var loggedObj = new Proxy(obj, { + get: function(target, name) { + console.log("get", target, name); + return Reflect.get(target, name); + } +}); +``` + +## Reflect对象的方法 + +Reflect对象的方法清单如下。 + +- Reflect.getOwnPropertyDescriptor(target,name) +- Reflect.defineProperty(target,name,desc) +- Reflect.getOwnPropertyNames(target) +- Reflect.getPrototypeOf(target) +- Reflect.deleteProperty(target,name) +- Reflect.enumerate(target) +- Reflect.freeze(target) +- Reflect.seal(target) +- Reflect.preventExtensions(target) +- Reflect.isFrozen(target) +- Reflect.isSealed(target) +- Reflect.isExtensible(target) +- Reflect.has(target,name) +- Reflect.hasOwn(target,name) +- Reflect.keys(target) +- Reflect.get(target,name,receiver) +- Reflect.set(target,name,value,receiver) +- Reflect.apply(target,thisArg,args) +- Reflect.construct(target,args) + +上面这些方法的作用,大部分与Object对象的同名方法的作用都是相同的。下面是对其中几个方法的解释。 + +**(1)Reflect.get(target,name,receiver)** + +查找并返回target对象的name属性,如果没有该属性,则返回undefined。 + +如果name属性部署了读取函数,则读取函数的this绑定receiver。 + +```javascript +var obj = { + get foo() { return this.bar(); }, + bar: function() { ... } +} + +// 下面语句会让 this.bar() +// 变成调用 wrapper.bar() +Reflect.get(obj, "foo", wrapper); +``` + +**(2)Reflect.set(target, name, value, receiver)** + +设置target对象的name属性等于value。如果name属性设置了赋值函数,则赋值函数的this绑定receiver。 + +**(3)Reflect.has(obj, name)** + +等同于`name in obj`。 + +**(4)Reflect.deleteProperty(obj, name)** + +等同于`delete obj[name]`。 + +**(5)Reflect.construct(target, args)** + +等同于`new target(...args)`,这提供了一种不使用new,来调用构造函数的方法。 + +**(6)Reflect.getPrototypeOf(obj)** + +读取对象的\_\_proto\_\_属性,等同于`Object.getPrototypeOf(obj)`。 + +**(7)Reflect.setPrototypeOf(obj, newProto)** + +设置对象的\_\_proto\_\_属性。注意,Object对象没有对应这个方法的方法。 + +**(8)Reflect.apply(fun,thisArg,args)** + +等同于`Function.prototype.apply.call(fun,thisArg,args)`。一般来说,如果要绑定一个函数的this对象,可以这样写`fn.apply(obj, args)`,但是如果函数定义了自己的apply方法,就只能写成`Function.prototype.apply.call(fn, obj, args)`,采用Reflect对象可以简化这种操作。 + +另外,需要注意的是,Reflect.set()、Reflect.defineProperty()、Reflect.freeze()、Reflect.seal()和Reflect.preventExtensions()返回一个布尔值,表示操作是否成功。它们对应的Object方法,失败时都会抛出错误。 + +```javascript +// 失败时抛出错误 +Object.defineProperty(obj, name, desc); +// 失败时返回false +Reflect.defineProperty(obj, name, desc); +``` + +上面代码中,Reflect.defineProperty方法的作用与Object.defineProperty是一样的,都是为对象定义一个属性。但是,Reflect.defineProperty方法失败时,不会抛出错误,只会返回false。 diff --git a/docs/reference.md b/docs/reference.md index 47abdda14..4efe1ef03 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -114,18 +114,22 @@ - Jafar Husain, [Asynchronous Generators for ES7](https://github.com/jhusain/asyncgenerator): Async函数的深入讨论 - Nolan Lawson, [Taming the asynchronous beast with ES7](http://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html): async函数通俗的实例讲解 -## Class与模块 +## 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的入门介绍 - 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语法的详细介绍和设计思想分析 +- Eric Faust, [ES6 In Depth: Subclassing](https://hacks.mozilla.org/2015/08/es6-in-depth-subclassing/): Class语法的深入介绍 + +## Decorator + - Maximiliano Fierro, [Declarative vs Imperative](http://elmasse.github.io/js/decorators-bindings-es7.html): Decorators和Mixin介绍 - Addy Osmani, [Exploring ES2016 Decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841): Decorator的深入介绍 - Sebastian McKenzie, [Allow decorators for functions as well](https://github.com/wycats/javascript-decorators/issues/4): 为什么修饰器不能用于函数 - Maximiliano Fierro, [Traits with ES7 Decorators](http://cocktailjs.github.io/blog/traits-with-es7-decorators.html): Trait的用法介绍 -## 模块 +## Module - 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规格的详细比较 diff --git a/sidebar.md b/sidebar.md index 9c72668a0..e1e9a694b 100644 --- a/sidebar.md +++ b/sidebar.md @@ -16,6 +16,7 @@ 1. [对象的扩展](#docs/object) 1. [函数的扩展](#docs/function) 1. [Symbol](#docs/symbol) +1. [Proxy和Reflect](#docs/proxy) 1. [Set和Map数据结构](#docs/set-map) 1. [Iterator和for...of循环](#docs/iterator) 1. [Generator函数](#docs/generator) From 6d259c33889e9e0435571d17668eb05d2e6b84bc Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 18 Aug 2015 09:02:16 +0800 Subject: [PATCH 0028/1267] edit Map --- docs/class.md | 2 +- docs/reference.md | 8 +++- docs/set-map.md | 108 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/docs/class.md b/docs/class.md index b644ffc2a..00ee6741d 100644 --- a/docs/class.md +++ b/docs/class.md @@ -335,7 +335,7 @@ class Foo {} ### 基本用法 -Class之间可以通过extends关键字,实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。 +Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。 ```javascript class ColorPoint extends Point {} diff --git a/docs/reference.md b/docs/reference.md index 4efe1ef03..6c2b2dd69 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -45,6 +45,7 @@ - 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结构的详细介绍 - Jason Orendorff, [ES6 In Depth: Collections](https://hacks.mozilla.org/2015/06/es6-in-depth-collections/):Set和Map结构的设计思想 +- Axel Rauschmayer, [Converting ES6 Maps to and from JSON](http://www.2ality.com/2015/08/es6-map-json.html): 如何将Map与其他数据结构互相转换 ## 字符串 @@ -60,9 +61,12 @@ ## 对象 -- 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对象的双向绑定 + +## Proxy和Reflect + +- Nicholas C. Zakas, [Creating defensive objects with ES6 proxies](http://www.nczonline.net/blog/2014/04/22/creating-defensive-objects-with-es6-proxies/) - 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实现元编程 - Tom Van Cutsem, [Harmony-reflect](https://github.com/tvcutsem/harmony-reflect/wiki): Reflect对象的设计目的 @@ -85,7 +89,6 @@ - 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的详细介绍 - Kyle Simpson, [Iterating ES6 Numbers](http://blog.getify.com/iterating-es6-numbers/): 在数值对象上部署遍历器 -- Mahdi Dibaiee, [ES7 Array and Generator comprehensions](http://dibaiee.ir/es7-array-generator-comprehensions/):ES7的Generator推导 ## Generator @@ -100,6 +103,7 @@ - Harold Cooper, [Coroutine Event Loops in Javascript](http://syzygy.st/javascript-coroutines/): Generator用于实现状态机 - Ruslan Ismagilov, [learn-generators](https://github.com/isRuslan/learn-generators): 编程练习,共6道题 - Steven Sanderson, [Experiments with Koa and JavaScript Generators](http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/): Generator入门介绍,以Koa框架为例 +- Mahdi Dibaiee, [ES7 Array and Generator comprehensions](http://dibaiee.ir/es7-array-generator-comprehensions/):ES7的Generator推导 ## Promise对象 diff --git a/docs/set-map.md b/docs/set-map.md index a689c2262..126fbf05d 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -322,7 +322,7 @@ data["[Object HTMLDivElement]"] // metadata 上面代码原意是将一个DOM节点作为对象data的键,但是由于对象只接受字符串作为键名,所以element被自动转为字符串`[Object HTMLDivElement]`。 -为了解决这个问题,ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。 +为了解决这个问题,ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。 ```javascript var m = new Map(); @@ -586,6 +586,110 @@ map.forEach(function(value, key, map) { 上面代码中,forEach方法的回调函数的this,就指向reporter。 +### 与其他数据结构的互相转换 + +**(1)Map转为数组** + +前面已经提过,Map转为数组最方便的方法,就是使用扩展运算符(...)。 + +```javascript +let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']); +[...myMap] +// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ] +``` + +**(2)数组转为Map** + +将数组转入Map构造函数,就可以转为Map。 + +```javascript +new Map([[true, 7], [{foo: 3}, ['abc']]]) +// Map {true => 7, Object {foo: 3} => ['abc']} +``` + +**(3)Map转为对象** + +如果所有Map的键都是字符串,它可以转为对象。 + +```javascript +function strMapToObj(strMap) { + let obj = Object.create(null); + for (let [k,v] of strMap) { + obj[k] = v; + } + return obj; +} + +let myMap = new Map().set('yes', true).set('no', false); +strMapToObj(myMap) +// { yes: true, no: false } +``` + +**(4)对象转为Map** + +```javascript +function objToStrMap(obj) { + let strMap = new Map(); + for (let k of Object.keys(obj)) { + strMap.set(k, obj[k]); + } + return strMap; +} + +objToStrMap({yes: true, no: false}) +// [ [ 'yes', true ], [ 'no', false ] ] +``` + +**(5)Map转为JSON** + +Map转为JSON要区分两种情况。一种情况是,Map的键名都是字符串,这时可以选择转为对象JSON。 + +```javascript +function strMapToJson(strMap) { + return JSON.stringify(strMapToObj(strMap)); +} + +let myMap = new Map().set('yes', true).set('no', false); +strMapToJson(myMap) +// '{"yes":true,"no":false}' +``` + +另一种情况是,Map的键名有非字符串,这时可以选择转为数组JSON。 + +```javascript +function mapToArrayJson(map) { + return JSON.stringify([...map]); +} + +let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']); +mapToArrayJson(myMap) +// '[[true,7],[{"foo":3},["abc"]]]' +``` + +**(6)JSON转为Map** + +JSON转为Map,正常情况下,所有键名都是字符串。 + +```javascript +function jsonToStrMap(jsonStr) { + return objToStrMap(JSON.parse(jsonStr)); +} + +jsonToStrMap('{"yes":true,"no":false}') +// Map {'yes' => true, 'no' => false} +``` + +但是,有一种特殊情况,整个JSON就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为Map。这往往是数组转为JSON的逆操作。 + +```javascript +function jsonToMap(jsonStr) { + return new Map(JSON.parse(jsonStr)); +} + +jsonToMap('[[true,7],[{"foo":3},["abc"]]]') +// Map {true => 7, Object {foo: 3} => ['abc']} +``` + ## WeakMap WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受原始类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。 @@ -595,7 +699,6 @@ WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制 下面是WeakMap结构的一个例子,可以看到用法上与Map几乎一样。 ```javascript - var wm = new WeakMap(); var element = document.querySelector(".element"); @@ -605,7 +708,6 @@ wm.get(element) // "Original" element.parentNode.removeChild(element); element = null; wm.get(element) // undefined - ``` 上面代码中,变量wm是一个WeakMap实例,我们将一个DOM节点element作为键名,然后销毁这个节点,element对应的键就自动消失了,再引用这个键名就返回undefined。 From 66932d04189a4d435f16d63c61241e4e7da4c26e Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 25 Aug 2015 13:41:49 +0800 Subject: [PATCH 0029/1267] =?UTF-8?q?add=20object/=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E8=BF=90=E7=AE=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/iterator.md | 4 ++ docs/object.md | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/docs/iterator.md b/docs/iterator.md index ea4e0f529..7c4b3492a 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -227,6 +227,10 @@ let obj = { ```javascript NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; +// 或者 +NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]; + +[...document.querySelectorAll('div')] // 可以执行了 ``` 如果Symbol.iterator方法返回的不是遍历器,解释引擎将会报错。 diff --git a/docs/object.md b/docs/object.md index 7062fa9ca..2e3f9cb8c 100644 --- a/docs/object.md +++ b/docs/object.md @@ -481,3 +481,119 @@ Object.unobserve(o, observer); ``` 注意,Object.observe和Object.unobserve这两个方法不属于ES6,而是属于ES7的一部分。不过,Chrome浏览器从33版起就已经支持。 + +## 对象的扩展运算符 + +目前,ES7有一个[提案](https://github.com/sebmarkbage/ecmascript-rest-spread),将rest参数/扩展运算符(...)引入对象。Babel转码器已经支持这项功能。 + +**(1)Rest参数** + +如果Rest参数用于从一个对象取值,就等于将所有可遍历、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。 + +```javascript +let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; +x // 1 +y // 2 +z // { a: 3, b: 4 } +``` + +上面代码中,变量z是Rest参数所在的对象。它获取等号右边的所有尚未读取的键(a和b),将它们和它们的值拷贝过来。 + +注意,Rest参数的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么Rest参数拷贝的是这个值的引用,而不是这个值的副本。 + +```javascript +let obj = { a: { b: 1 } }; +let { ...x } = obj; +obj.a.b = 2; +x.a.b // 2 +``` + +上面代码中,x是Rest参数,拷贝了对象obj的a属性。a属性引用了一个对象,修改这个对象的值,会影响到Rest参数对它的引用。 + +另外,Rest参数不会拷贝继承自原型对象的属性。 + +```javascript +let o1 = { a: 1 }; +let o2 = { b: 2 }; +o2.__proto__ = o1; +let o3 = { ...o2 }; +o3 // { b: 2 } +``` + +上面代码中,对象o3是o2的复制,但是只复制了o2自身的属性,没有复制它的原型对象o1的属性。 + +**(2)扩展运算符** + +如果扩展运算符用于一个对象,就会将该对象的所有可遍历属性,拷贝到一个新对象,然后返回这个新对象。 + +```javascript +let z = { a: 3, b: 4 }; +let n = { ...z }; +n // { a: 3, b: 4 } +``` + +对象的扩展运算符,等同于使用`Object.assign`方法。 + +```javascript +let aClone = { ...a }; +// 等同于 +let aClone = Object.assign({}, a); +``` + +扩展运算符可以用于合并两个对象。 + +```javascript +let ab = { ...a, ...b }; +``` + +扩展运算符还可以用自定义属性,会在新对象之中,覆盖掉原有参数。 + +```javascript +let aWithOverrides = { ...a, x: 1, y: 2 }; +// 等同于 +let aWithOverrides = { ...a, ...{ x: 1, y: 2 } }; +// 等同于 +let x = 1, y = 2, aWithOverrides = { ...a, x, y }; +// 等同于 +let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 }); +``` + +上面代码中,a对象的x属性和y属性,拷贝到新对象后会被覆盖掉。 + +如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。 + +```javascript +let aWithDefaults = { x: 1, y: 2, ...a }; +// 等同于 +let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a); +// 等同于 +let aWithDefaults = Object.assign({ x: 1, y: 2 }, a); +``` + +扩展运算符的参数对象之中,如果有取值函数`get`,这个函数是会执行的。 + +```javascript +// 并不会抛出错误,因为x属性只是被定义,但没执行 +let aWithXGetter = { + ...a, + get x() { + throws new Error('not thrown yet'); + } +}; + +// 会抛出错误,因为x属性被执行了 +let runtimeError = { + ...a, + ...{ + get x() { + throws new Error('thrown now'); + } + } +}; +``` + +如果扩展运算符的参数是null或undefined,这个两个值会被忽略,不会报错。 + +```javascript +let emptyObject = { ...null, ...undefined }; // 不报错 +``` From 8fa161cceb9cb7bdaa14153c4576aa918aa57d71 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 25 Aug 2015 13:56:59 +0800 Subject: [PATCH 0030/1267] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BE=A7=E6=A0=8F?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/object.md | 6 +++--- sidebar.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/object.md b/docs/object.md index 2e3f9cb8c..2693617e7 100644 --- a/docs/object.md +++ b/docs/object.md @@ -488,7 +488,7 @@ Object.unobserve(o, observer); **(1)Rest参数** -如果Rest参数用于从一个对象取值,就等于将所有可遍历、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。 +Rest参数用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。 ```javascript let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; @@ -524,7 +524,7 @@ o3 // { b: 2 } **(2)扩展运算符** -如果扩展运算符用于一个对象,就会将该对象的所有可遍历属性,拷贝到一个新对象,然后返回这个新对象。 +扩展运算符用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。 ```javascript let z = { a: 3, b: 4 }; @@ -532,7 +532,7 @@ let n = { ...z }; n // { a: 3, b: 4 } ``` -对象的扩展运算符,等同于使用`Object.assign`方法。 +这等同于使用`Object.assign`方法。 ```javascript let aClone = { ...a }; diff --git a/sidebar.md b/sidebar.md index e1e9a694b..c5d00decc 100644 --- a/sidebar.md +++ b/sidebar.md @@ -13,8 +13,8 @@ 1. [正则的扩展](#docs/regex) 1. [数值的扩展](#docs/number) 1. [数组的扩展](#docs/array) -1. [对象的扩展](#docs/object) 1. [函数的扩展](#docs/function) +1. [对象的扩展](#docs/object) 1. [Symbol](#docs/symbol) 1. [Proxy和Reflect](#docs/proxy) 1. [Set和Map数据结构](#docs/set-map) From 7b116a976cba88e2ad7fc1fea6dc5e8b9025cbd5 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 26 Aug 2015 07:23:50 +0800 Subject: [PATCH 0031/1267] fix typo of object.md --- docs/async.md | 8 ++++---- docs/string.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/async.md b/docs/async.md index 1bf0424fb..a7c9c8b66 100644 --- a/docs/async.md +++ b/docs/async.md @@ -642,13 +642,13 @@ function next(ret) { 上面代码中,next 函数的内部代码,一共只有四行命令。 -- 第一行,检查当前是否为 Generator 函数的最后一步,如果是就返回。 +第一行,检查当前是否为 Generator 函数的最后一步,如果是就返回。 -- 第二行,确保每一步的返回值,是 Promise 对象。 +第二行,确保每一步的返回值,是 Promise 对象。 -- 第三行,使用 then 方法,为返回值加上回调函数,然后通过 onFulfilled 函数再次调用 next 函数。 +第三行,使用 then 方法,为返回值加上回调函数,然后通过 onFulfilled 函数再次调用 next 函数。 -- 第四行,在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为 rejected,从而终止执行。 +第四行,在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为 rejected,从而终止执行。 ### 处理并发的异步操作 diff --git a/docs/string.md b/docs/string.md index fe4fa96bc..3e7e6a097 100644 --- a/docs/string.md +++ b/docs/string.md @@ -308,7 +308,7 @@ console.log(`foo ${fn()} bar`); // foo Hello World bar ``` -如果大括号中的值不是字符串,将按照一般的规则转为字符串。不如,大括号中是一个对象,将默认调用对象的toString方法。 +如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。 如果模板字符串中的变量没有声明,将报错。 @@ -456,7 +456,7 @@ i18n`Hello ${name}, you have ${amount}:c(CAD) in your bank account.` // Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto. ``` -模板字符串本身并不能取代Mustache之类的模板函数,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。 +模板字符串本身并不能取代Mustache之类的模板库,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。 ```javascript // 下面的hashTemplate函数 From 417b7c825ab2fe333d5fdacd921fcc18d4760de0 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 26 Aug 2015 07:34:23 +0800 Subject: [PATCH 0032/1267] edit object/template string --- docs/string.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/string.md b/docs/string.md index 3e7e6a097..fb6533606 100644 --- a/docs/string.md +++ b/docs/string.md @@ -350,14 +350,14 @@ tag函数的其他参数,都是模板字符串各个变量被替换后的值 tag函数所有参数的实际值如下。 -- 第一个参数:['Hello ', ' world '] +- 第一个参数:['Hello ', ' world ', ''] - 第二个参数: 15 - 第三个参数:50 也就是说,tag函数实际上以下面的形式调用。 ```javascript -tag(['Hello ', ' world '], 15, 50) +tag(['Hello ', ' world ', ''], 15, 50) ``` 我们可以按照需要编写tag函数的代码。下面是tag函数的一种写法,以及运行结果。 @@ -369,6 +369,7 @@ var b = 10; function tag(s, v1, v2) { console.log(s[0]); console.log(s[1]); + console.log(s[2]); console.log(v1); console.log(v2); @@ -378,6 +379,7 @@ function tag(s, v1, v2) { tag`Hello ${ a + b } world ${ a * b}`; // "Hello " // " world " +// "" // 15 // 50 // "OK" @@ -413,7 +415,7 @@ msg passthru函数采用rest参数的写法如下。 ```javascript -function passthru(literals,...values) { +function passthru(literals, ...values) { var output = ""; for (var index = 0; index < values.length; index++) { output += literals[index] + values[index]; From f9473833c7365e0ea2cb61068407491f15eb2af8 Mon Sep 17 00:00:00 2001 From: Milk Lee Date: Wed, 26 Aug 2015 18:02:07 +0800 Subject: [PATCH 0033/1267] =?UTF-8?q?=E4=B8=80=E5=A4=84=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这里的`数值`应该改为`数组` --- docs/iterator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iterator.md b/docs/iterator.md index 7c4b3492a..ef6f79ba1 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -223,7 +223,7 @@ let obj = { }; ``` -对于类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数值的Iterator接口。 +对于类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数组的Iterator接口。 ```javascript NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; From 04d80fcbb446e89b88284d532d597de647e99652 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 27 Aug 2015 08:44:49 +0800 Subject: [PATCH 0034/1267] edit function/arrow function --- docs/decorator.md | 2 +- docs/function.md | 20 ++++++++++++++------ docs/style.md | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/decorator.md b/docs/decorator.md index 7e1abace0..e6f943584 100644 --- a/docs/decorator.md +++ b/docs/decorator.md @@ -413,7 +413,7 @@ Trait也是一种修饰器,功能与Mixin类型,但是提供更多功能, 下面采用[traits-decorator](https://github.com/CocktailJS/traits-decorator)这个第三方模块作为例子。这个模块提供的traits修饰器,不仅可以接受对象,还可以接受ES6类作为参数。 ```javascript -import {traits } from 'traits-decorator' +import { traits } from 'traits-decorator' class TFoo { foo() { console.log('foo') } diff --git a/docs/function.md b/docs/function.md index e830d423d..9cf38e671 100644 --- a/docs/function.md +++ b/docs/function.md @@ -427,7 +427,6 @@ Generator函数运行后,返回一个遍历器对象,因此也可以使用 ```javascript - var go = function*(){ yield 1; yield 2; @@ -435,13 +434,14 @@ var go = function*(){ }; [...go()] // [1, 2, 3] - ``` 上面代码中,变量go是一个Generator函数,执行后返回的是一个遍历器,对这个遍历器执行扩展运算符,就会将内部遍历得到的值,转为一个数组。 ## 箭头函数 +### 基本用法 + ES6允许使用“箭头”(=>)定义函数。 ```javascript @@ -540,13 +540,19 @@ headAndTail(1, 2, 3, 4, 5) // [1,[2,3,4,5]] ``` +### 使用注意点 + 箭头函数有几个使用注意点。 -- 函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象。 -- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 -- 不可以使用arguments对象,该对象在函数体内不存在。 +(1)函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象。 -上面三点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。下面的代码是一个例子,将this对象绑定定义时所在的对象。 +(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 + +(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。 + +(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。 + +上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。下面的代码是一个例子,将this对象绑定定义时所在的对象。 ```javascript var handler = { @@ -569,6 +575,8 @@ var handler = { 长期以来,JavaScript语言的this对象一直是一个令人头痛的问题,在对象方法中使用this,必须非常小心。箭头函数绑定this,很大程度上解决了这个困扰。 +### 嵌套的箭头函数 + 箭头函数内部,还可以再使用箭头函数。下面是一个ES5语法的多重嵌套函数。 ```javascript diff --git a/docs/style.md b/docs/style.md index e43db80ae..5f02430c5 100644 --- a/docs/style.md +++ b/docs/style.md @@ -1,6 +1,6 @@ # 编程风格 -本章探讨如何将ES6的新语法,运用到编码实践之中,与传统的JavaScript语法结合在一起,写出合理的、易于阅读和维护的代码。多家公司和组织已经公开了它们的风格规范,具体可参阅[jscs.info](http://jscs.info/),下面的内容主要参考了[Airbnb](http://jscs.info/)的JavaScript风格规范。 +本章探讨如何将ES6的新语法,运用到编码实践之中,与传统的JavaScript语法结合在一起,写出合理的、易于阅读和维护的代码。多家公司和组织已经公开了它们的风格规范,具体可参阅[jscs.info](http://jscs.info/),下面的内容主要参考了[Airbnb](https://github.com/airbnb/javascript)的JavaScript风格规范。 ## 块级作用域 From 68c9ff389524e30bc5d88f9653aef256b2886d7a Mon Sep 17 00:00:00 2001 From: Milk Lee Date: Fri, 28 Aug 2015 08:50:18 +0800 Subject: [PATCH 0035/1267] =?UTF-8?q?=E6=BC=8F=E4=BA=86=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 加上会让语句通顺一点 --- docs/intro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.md b/docs/intro.md index e625f1290..46a2906a5 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -120,7 +120,7 @@ $ es-checker ## Babel转码器 -[Babel](https://babeljs.io/)是一个广泛使用的ES6转码器,可以ES6代码转为ES5代码,从而在浏览器或其他环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。 +[Babel](https://babeljs.io/)是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在浏览器或其他环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。 ```javascript // 转码前 From 6bf574aeb55aa85cb1fa9ed998aaf37ad8d1b8db Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 28 Aug 2015 10:25:45 +0800 Subject: [PATCH 0036/1267] edit decorator --- docs/decorator.md | 2 +- docs/promise.md | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/decorator.md b/docs/decorator.md index e6f943584..c54ba3ba1 100644 --- a/docs/decorator.md +++ b/docs/decorator.md @@ -533,7 +533,7 @@ $ babel --optional es7.decorators 脚本中打开的命令如下。 ```javascript -babel.transfrom("code", {optional: ["es7.decorators"]}) +babel.transform("code", {optional: ["es7.decorators"]}) ``` Babel的官方网站提供一个[在线转码器](https://babeljs.io/repl/),只要勾选Experimental,就能支持Decorator的在线转码。 diff --git a/docs/promise.md b/docs/promise.md index bf46f792f..7ef95411e 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -56,7 +56,7 @@ then方法可以接受两个回调函数作为参数。第一个回调函数是P ```javascript function timeout(ms) { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); }); } @@ -68,6 +68,26 @@ timeout(100).then((value) => { 上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为Resolved,就会触发then方法绑定的回调函数。 +下面是异步加载图片的例子。 + +```javascript +function loadImageAsync(url) { + return new Promise(function(resolve, reject) { + var image = new Image(); + + image.onload = function() { + resolve(image); + }; + + image.onerror = function() { + reject(new Error('Could not load image at ' + url)); + }; + + image.src = url; + }); +} +``` + 下面是一个用Promise对象实现的Ajax操作的例子。 ```javascript From fe581453221eeefcd9cace9eb738fc6558c19db8 Mon Sep 17 00:00:00 2001 From: Feng Hao Date: Fri, 28 Aug 2015 11:06:16 +0800 Subject: [PATCH 0037/1267] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Array.prototyp.slice 改为 Array.prototype.slice --- docs/array.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/array.md b/docs/array.md index 94ad0429c..24b0da382 100644 --- a/docs/array.md +++ b/docs/array.md @@ -31,7 +31,7 @@ Array.from({ 0: "a", 1: "b", 2: "c", length: 3 }); // [ "a", "b" , "c" ] ``` -对于还没有部署该方法的浏览器,可以用Array.prototyp.slice方法替代。 +对于还没有部署该方法的浏览器,可以用Array.prototype.slice方法替代。 ```javascript const toArray = (() => From 80240a574e69aee7b9b84f838c8f97312d3233d3 Mon Sep 17 00:00:00 2001 From: Feng Hao Date: Fri, 28 Aug 2015 11:11:31 +0800 Subject: [PATCH 0038/1267] Update object.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除重复示例 --- docs/object.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/object.md b/docs/object.md index 2693617e7..f5fc3c70c 100644 --- a/docs/object.md +++ b/docs/object.md @@ -145,18 +145,6 @@ person.firstName.name // "get firstName" 上面代码中,方法的name属性返回函数名(即方法名)。如果使用了取值函数,则会在方法名前加上get。如果是存值函数,方法名的前面会加上set。 -```javascript -var doSomething = function() { - // ... -}; - -doSomething.bind().name -// "bound doSomething" - -(new Function()).name -// "anonymous" -``` - 有两种特殊情况:bind方法创造的函数,name属性返回“bound”加上原函数的名字;Function构造函数创造的函数,name属性返回“anonymous”。 ```javascript From 5a5f9d8d492d0f925cbb6e09b10ebed9d2078d40 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 28 Aug 2015 18:51:35 +0800 Subject: [PATCH 0039/1267] fix #81 --- docs/class.md | 16 ++++++++++++++-- docs/generator.md | 12 ++++++------ docs/iterator.md | 12 +++--------- docs/module.md | 6 +++--- docs/promise.md | 4 ++-- docs/proxy.md | 5 +++-- docs/symbol.md | 2 +- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/docs/class.md b/docs/class.md index 00ee6741d..3032c59ec 100644 --- a/docs/class.md +++ b/docs/class.md @@ -53,7 +53,7 @@ typeof Point // "function" 上面代码表明,类的数据类型就是函数。 -构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,除了constructor方法以外,类的方法都定义在类的prototype属性上面。 +构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。 ```javascript class Point { @@ -73,11 +73,23 @@ class Point { // 等同于 Point.prototype = { + constructor(){}, toString(){}, toValue(){} } ``` +在类的实例上面调用方法,其实就是调用原型上的方法。 + +```javascript +class B {} +let b = new B(); + +b.constructor === B.prototype.constructor // true +``` + +上面代码中,b是B类的实例,它的constructor方法就是B类原型的constructor方法。 + 由于类的方法(除constructor以外)都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。`Object.assign`方法可以很方便地一次向类添加多个方法。 ```javascript @@ -419,7 +431,7 @@ cp instanceof Point // true ### 类的prototype属性和\_\_proto\_\_属性 -在ES5中,每一个对象都有`__proto__`属性,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同时有prototype属性和`__proto__`属性,因此同时存在两条继承链。 +大多数浏览器的ES5实现之中,每一个对象都有`__proto__`属性,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同时有prototype属性和`__proto__`属性,因此同时存在两条继承链。 (1)子类的`__proto__`属性,表示构造函数的继承,总是指向父类。 diff --git a/docs/generator.md b/docs/generator.md index 8a4150c38..56cb8af23 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -150,9 +150,9 @@ for (var f of flat(arr)){ ### 与Iterator的关系 -上一章说过,任意一个对象的Symbol.iterator属性,等于该对象的遍历器函数,调用该函数会返回该对象的一个遍历器。 +上一章说过,任意一个对象的`Symbol.iterator`方法,等于该对象的遍历器函数,调用该函数会返回该对象的一个遍历器。 -遍历器本身也是一个对象,它的Symbol.iterator属性执行后,返回自身。 +遍历器本身也是一个对象,它的`Symbol.iterator`方法执行后,返回自身。 ```javascript function* gen(){ @@ -331,7 +331,7 @@ try { 上面代码之所以只捕获了a,是因为函数体外的catch语句块,捕获了抛出的a错误以后,就不会再继续执行try语句块了。 -如果遍历器函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获。 +如果Generator函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获。 ```javascript var g = function* () { @@ -355,7 +355,7 @@ try { 上面代码中,遍历器函数g内部,没有部署try...catch代码块,所以抛出的错误直接被外部catch代码块捕获。 -如果遍历器函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历,否则遍历直接终止。 +如果Generator函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历,否则遍历直接终止。 ```javascript var gen = function* gen(){ @@ -374,7 +374,7 @@ try { // hello ``` -上面代码只输出hello就结束了,因为第二次调用next方法时,遍历器状态已经变成终止了。但是,如果使用throw方法抛出错误,不会影响遍历器状态。 +上面代码只输出hello就结束了,因为第二次调用next方法时,遍历器状态已经变成终止了。但是,如果使用throw命令抛出错误,不会影响遍历器状态。 ```javascript var gen = function* gen(){ @@ -529,7 +529,7 @@ for(let value of delegatingIterator) { // "Ok, bye." ``` -上面代码中,delegatingIterator是代理者,delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Genertor函数,有递归的效果。 +上面代码中,delegatingIterator是代理者,delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Generator函数,有递归的效果。 yield*语句等同于在Generator函数内部,部署一个for...of循环。 diff --git a/docs/iterator.md b/docs/iterator.md index 7c4b3492a..97f261186 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -95,7 +95,7 @@ interface IterationResult { Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(详见下文)。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。 -ES6规定,默认的Iterator接口部署在数据结构的`Symbol.iterator`属性,或者一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”(iterable)。也就是说,调用`Symbol.iterator`方法,就会得到当前数据结构的默认遍历器。`Symbol.iterator`本身是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一节)。 +ES6规定,默认的Iterator接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”(iterable)。调用`Symbol.iterator`方法,就会得到当前数据结构的默认遍历器。`Symbol.iterator`本身是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一章)。 在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。 @@ -223,7 +223,7 @@ let obj = { }; ``` -对于类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数值的Iterator接口。 +对于类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数组的Iterator接口。 ```javascript NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; @@ -265,10 +265,9 @@ while (!$result.done) { **(1)解构赋值** -对数组和Set结构进行解构赋值时,会默认调用iterator接口。 +对数组和Set结构进行解构赋值时,会默认调用`Symbol.iterator`方法。 ```javascript - let set = new Set().add('a').add('b').add('c'); let [x,y] = set; @@ -276,7 +275,6 @@ let [x,y] = set; let [first, ...rest] = set; // first='a'; rest=['b','c']; - ``` **(2)扩展运算符** @@ -337,7 +335,6 @@ iterator.next() // { value: undefined, done: true } 《数组的扩展》一章中提到,ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器。 ```javascript - var arr = [1, 5, 7]; var arrEntries = arr.entries(); @@ -346,7 +343,6 @@ arrEntries.toString() arrEntries === arrEntries[Symbol.iterator]() // true - ``` 上面代码中,entries方法返回的是一个遍历器(iterator),本质上就是调用了`Symbol.iterator`方法。 @@ -354,7 +350,6 @@ arrEntries === arrEntries[Symbol.iterator]() 字符串是一个类似数组的对象,也原生具有Iterator接口。 ```javascript - var someString = "hi"; typeof someString[Symbol.iterator] // "function" @@ -364,7 +359,6 @@ 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方法,实现对于字符串的遍历。 diff --git a/docs/module.md b/docs/module.md index 6de58fee3..2af3f9e87 100644 --- a/docs/module.md +++ b/docs/module.md @@ -67,7 +67,7 @@ export function multiply (x, y) { import {firstName, lastName, year} from './profile'; -function sfirsetHeader(element) { +function setName(element) { element.textContent = firstName + ' ' + lastName; } ``` @@ -124,7 +124,7 @@ export function circumference(radius) { } ``` -然后,main.js文件输入circlek.js模块。 +然后,main.js文件输入circle.js模块。 ```javascript // main.js @@ -296,7 +296,7 @@ export { area as circleArea } from 'circle'; module math from "circleplus"; import exp from "circleplus"; -console.log(exp(math.pi)); +console.log(exp(math.PI)); ``` 上面代码中的"import exp"表示,将circleplus模块的默认方法加载为exp方法。 diff --git a/docs/promise.md b/docs/promise.md index 7ef95411e..4895158cc 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -260,7 +260,7 @@ someAsyncThing().then(function() { }); ``` -上面代码中,someAsyncThing函数产生的Promise对象会报错,但是由于没有调用catch方法,这个错误不会被捕获,也不会传递到外层代码,导致运行后没有任何输出。 +上面代码中,someAsyncThing函数产生的Promise对象会报错,但是由于没有指定catch方法,这个错误不会被捕获,也不会传递到外层代码,导致运行后没有任何输出。 ```javascript var promise = new Promise(function(resolve, reject) { @@ -272,7 +272,7 @@ promise.then(function(value) { console.log(value) }); // Uncaught Error: test ``` -上面代码中,Promise指定在下一轮“事件循环”再抛出错误,结果由于没有指定catch语句,就冒泡到最外层,成了未捕获的错误。 +上面代码中,Promise指定在下一轮“事件循环”再抛出错误,结果由于没有指定使用try...catch语句,就冒泡到最外层,成了未捕获的错误。因为此时,Promise的函数体已经运行结束了,所以这个错误是在Promise函数体外抛出的。 Node.js有一个unhandledRejection事件,专门监听未捕获的reject错误。 diff --git a/docs/proxy.md b/docs/proxy.md index 862932e8d..fe28f0caa 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -38,7 +38,7 @@ ES6原生提供Proxy构造函数,用来生成Proxy实例。 var proxy = new Proxy(target, handler) ``` -Proxy对象的所用用法,都是上面这种形式,不同的只是handler参数的写法。其中,`new Proxy()`表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。 +Proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,`new Proxy()`表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。 下面是另一个拦截读取属性行为的例子。 @@ -77,7 +77,7 @@ let obj = Object.create(proxy); obj.time // 35 ``` -上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所有根据原型链,会在proxy对象上读取该属性,导致被拦截。 +上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。 同一个拦截器函数,可以设置拦截多个操作。 @@ -368,6 +368,7 @@ Reflect对象的方法清单如下。 - Reflect.defineProperty(target,name,desc) - Reflect.getOwnPropertyNames(target) - Reflect.getPrototypeOf(target) +- Reflect.setPrototypeOf(target, prototype) - Reflect.deleteProperty(target,name) - Reflect.enumerate(target) - Reflect.freeze(target) diff --git a/docs/symbol.md b/docs/symbol.md index b45c5f8e1..a03826e21 100644 --- a/docs/symbol.md +++ b/docs/symbol.md @@ -4,7 +4,7 @@ ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。 -ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第八种数据类型,前七种是:数值、字符串、布尔值、数组、对象、函数、undefined。 +ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。 Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 From 6b1ee303ff4a15274f7d499abc1a9507256eb8d6 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 30 Aug 2015 15:13:43 +0800 Subject: [PATCH 0040/1267] edit destructuring and iterator --- docs/destructuring.md | 35 ++++++++++--- docs/intro.md | 18 +++++++ docs/iterator.md | 117 ++++++++++++++++++------------------------ docs/module.md | 2 +- 4 files changed, 95 insertions(+), 77 deletions(-) diff --git a/docs/destructuring.md b/docs/destructuring.md index b736af5c2..40e1214e0 100644 --- a/docs/destructuring.md +++ b/docs/destructuring.md @@ -44,13 +44,10 @@ tail // [2, 3, 4] ```javascript var [foo] = []; -var [foo] = 1; -var [foo] = false; -var [foo] = NaN; var [bar, foo] = [1]; ``` -以上几种情况都属于解构不成功,foo的值都会等于undefined。这是因为原始类型的值,会自动转为对象,比如数值1转为`new Number(1)`,从而导致foo取到undefined。 +以上两种情况都属于解构不成功,foo的值都会等于undefined。 另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。 @@ -65,18 +62,19 @@ b // 2 d // 4 ``` -上面代码的两个例子,都属于不完全解构,但是可以成功。 +上面两个例子,都属于不完全解构,但是可以成功。 -如果对undefined或null进行解构,会报错。 +如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。 ```javascript // 报错 +let [foo] = 1; +let [foo] = false; +let [foo] = NaN; let [foo] = undefined; let [foo] = null; ``` -这是因为解构只能用于数组或对象。其他原始类型的值都可以转为相应的对象,但是,undefined和null不能转为对象,因此报错。 - 解构赋值允许指定默认值。 ```javascript @@ -207,6 +205,27 @@ x // null 上面代码中,如果x属性等于null,就不严格相等于undefined,导致默认值不会生效。 +如果解构失败,变量的值等于undefined。 + +```javascript +var {foo} = {bar: 'baz'} +foo // undefined +``` + +如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。 + +```javascript +// 报错 +var {foo: {bar}} = {baz: 'baz'} +``` + +上面代码中,等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错,请看下面的代码。 + +```javascript +var _tmp = {baz: 'baz'}; +_tmp.foo.bar // 报错 +``` + 如果要将一个已经声明的变量用于解构赋值,必须非常小心。 ```javascript diff --git a/docs/intro.md b/docs/intro.md index 46a2906a5..ca24edcaa 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -116,6 +116,11 @@ $ node --v8-options | grep harmony ```bash $ npm install -g es-checker $ es-checker + +========================================= +Passes 24 feature Dectations +Your runtime supports 57% of ECMAScript 6 +========================================= ``` ## Babel转码器 @@ -207,6 +212,19 @@ Babel配合Browserify一起使用,可以生成浏览器能够直接加载的 $ browserify script.js -t babelify --outfile bundle.js ``` +在`package.json`设置下面的代码,就不用每次命令行都输入参数了。 + +```javascript +{ + // ... + "browserify": { + "transform": [ + ["babelify", { "stage": [0] }] + ] + } +} +`` + ## Traceur转码器 Google公司的[Traceur](https://github.com/google/traceur-compiler)转码器,也可以将ES6代码转为ES5代码。 diff --git a/docs/iterator.md b/docs/iterator.md index 97f261186..b179258cc 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -6,19 +6,19 @@ JavaScript原有的表示“集合”的数据结构,主要是数组(Array 遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。 -Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。 +Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令`for...of`循环,Iterator接口主要供`for...of`消费。 Iterator的遍历过程是这样的。 -(1)创建一个指针,指向当前数据结构的起始位置。也就是说,遍历器的返回值是一个指针对象。 +(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。 (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。 (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。 -(4)调用指针对象的next方法,直到它指向数据结构的结束位置。 +(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。 -每一次调用next方法,都会返回当前成员的信息,具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。 +每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。 下面是一个模拟next方法返回值的例子。 @@ -41,15 +41,15 @@ it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true } ``` -上面代码定义了一个makeIterator函数,它的作用就是返回数组的指针对象。对数组`['a', 'b']`执行这个函数,就会返回该数组的指针对象it。 +上面代码定义了一个makeIterator函数,它是一个遍历器生成函数,作用就是返回一个遍历器对象。对数组`['a', 'b']`执行这个函数,就会返回该数组的遍历器对象(即指针对象)it。 指针对象的next方法,用来移动指针。开始时,指针指向数组的开始位置。然后,每次调用next方法,指针就会指向数组的下一个成员。第一次调用,指向a;第二次调用,指向b。 next方法返回一个对象,表示当前数据成员的信息。这个对象具有value和done两个属性,value属性返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束,即是否还有必要再一次调用next方法。 -总之,指针对象具有next方法。调用next方法,就可以遍历事先给定的数据结构。 +总之,调用指针对象的next方法,就可以遍历事先给定的数据结构。 -由于Iterator只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器,或者说用遍历器模拟出数据结构。下面是一个无限运行的遍历器例子。 +由于Iterator只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。下面是一个无限运行的遍历器对象的例子。 ```javascript function idMaker(){ @@ -70,9 +70,9 @@ it.next().value // '2' // ... ``` -上面的例子中,遍历器idMaker函数返回的指针对象,并没有对应的数据结构,或者说遍历器自己描述了一个数据结构出来。 +上面的例子中,遍历器生成函数idMaker,返回一个遍历器对象(即指针对象)。但是并没有对应的数据结构,或者说,遍历器对象自己描述了一个数据结构出来。 -在ES6中,有些数据结构原生提供遍历器(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性(详见下文),另外一些数据结构没有。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个指针对象。 +在ES6中,有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性(详见下文),另外一些数据结构没有。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。 如果使用TypeScript的写法,遍历器接口(Iterable)、指针对象(Iterator)和next方法返回值的规格可以描述如下。 @@ -93,9 +93,9 @@ interface IterationResult { ## 数据结构的默认Iterator接口 -Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(详见下文)。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。 +Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即`for...of`循环(详见下文)。当使用`for...of`循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。 -ES6规定,默认的Iterator接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”(iterable)。调用`Symbol.iterator`方法,就会得到当前数据结构的默认遍历器。`Symbol.iterator`本身是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一章)。 +ES6规定,默认的Iterator接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”(iterable)。调用`Symbol.iterator`方法,就会得到当前数据结构默认的遍历器生成函数。`Symbol.iterator`本身是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一章)。 在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。 @@ -109,13 +109,13 @@ iter.next() // { value: 'c', done: false } iter.next() // { value: undefined, done: true } ``` -上面代码中,变量arr是一个数组,原生就具有遍历器接口,部署在arr的Symbol.iterator属性上面。所以,调用这个属性,就得到遍历器。 +上面代码中,变量arr是一个数组,原生就具有遍历器接口,部署在arr的Symbol.iterator属性上面。所以,调用这个属性,就得到遍历器对象。 -上面提到,原生就部署iterator接口的数据结构有三类,对于这三类数据结构,不用自己写遍历器,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的Iterator接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。 +上面提到,原生就部署iterator接口的数据结构有三类,对于这三类数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的Iterator接口,都需要自己在Symbol.iterator属性上面部署,这样才会被`for...of`循环遍历。 对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作Map结构使用,ES5没有Map结构,而ES6原生提供了。 -一个对象如果要有可被for...of循环调用的Iterator接口,就必须在Symbol.iterator的属性上部署遍历器方法(原型链上的对象具有该方法也可)。 +一个对象如果要有可被`for...of`循环调用的Iterator接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。 ```javascript class RangeIterator { @@ -146,7 +146,7 @@ for (var value of range(0, 3)) { } ``` -上面代码是一个类部署Iterator接口的写法。Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器。 +上面代码是一个类部署Iterator接口的写法。Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器对象。 下面是通过遍历器实现指针结构的例子。 @@ -233,7 +233,7 @@ NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]; [...document.querySelectorAll('div')] // 可以执行了 ``` -如果Symbol.iterator方法返回的不是遍历器,解释引擎将会报错。 +如果Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。 ```javascript var obj = {}; @@ -243,9 +243,9 @@ obj[Symbol.iterator] = () => 1; [...obj] // TypeError: [] is not a function ``` -上面代码中,变量obj的Symbol.iterator方法返回的不是遍历器,因此报错。 +上面代码中,变量obj的Symbol.iterator方法对应的不是遍历器生成函数,因此报错。 -有了遍历器接口,数据结构就可以用for...of循环遍历(详见下文),也可以使用while循环遍历。 +有了遍历器接口,数据结构就可以用`for...of`循环遍历(详见下文),也可以使用while循环遍历。 ```javascript var $iterator = ITERABLE[Symbol.iterator](); @@ -257,7 +257,7 @@ while (!$result.done) { } ``` -上面代码中,ITERABLE代表某种可遍历的数据结构,$iterator是它的遍历器。遍历器每次移动指针(next方法),都检查一下返回值的done属性,如果遍历还没结束,就移动遍历器的指针到下一步(next方法),不断循环。 +上面代码中,ITERABLE代表某种可遍历的数据结构,$iterator是它的遍历器对象。遍历器对象每次移动指针(next方法),都检查一下返回值的done属性,如果遍历还没结束,就移动遍历器对象的指针到下一步(next方法),不断循环。 ## 调用默认Iterator接口的场合 @@ -332,7 +332,7 @@ iterator.next() // { value: undefined, done: true } ## 原生具备Iterator接口的数据结构 -《数组的扩展》一章中提到,ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器。 +《数组的扩展》一章中提到,ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器对象。 ```javascript var arr = [1, 5, 7]; @@ -345,7 +345,7 @@ arrEntries === arrEntries[Symbol.iterator]() // true ``` -上面代码中,entries方法返回的是一个遍历器(iterator),本质上就是调用了`Symbol.iterator`方法。 +上面代码中,entries方法返回的是一个遍历器对象(iterator),本质上就是调用了`Symbol.iterator`方法。 字符串是一个类似数组的对象,也原生具有Iterator接口。 @@ -361,7 +361,7 @@ iterator.next() // { value: "i", done: false } iterator.next() // { value: undefined, done: true } ``` -上面代码中,调用`Symbol.iterator`方法返回一个遍历器,在这个遍历器上可以调用next方法,实现对于字符串的遍历。 +上面代码中,调用`Symbol.iterator`方法返回一个遍历器对象,在这个遍历器上可以调用next方法,实现对于字符串的遍历。 可以覆盖原生的`Symbol.iterator`方法,达到修改遍历器行为的目的。 @@ -422,26 +422,25 @@ for (let x of obj) { 上面代码中,`Symbol.iterator`方法几乎不用部署任何代码,只要用yield命令给出每一步的返回值即可。 -## 遍历器的return(),throw() +## 遍历器对象的return(),throw() -遍历器返回的指针对象除了具有next方法,还可以具有return方法和throw方法。其中,next方法是必须部署的,return方法和throw方法是否部署是可选的。 +遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。 -return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句或continue语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。 +return方法的使用场合是,如果`for...of`循环提前退出(通常是因为出错,或者有break语句或continue语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。 -throw方法主要是配合Generator函数使用,一般的遍历器用不到这个方法。请参阅《Generator函数》一章。 +throw方法主要是配合Generator函数使用,一般的遍历器对象用不到这个方法。请参阅《Generator函数》一章。 ## for...of循环 -ES6借鉴C++、Java、C#和Python语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。一个数据结构只要部署了`Symbol.iterator`方法,就被视为具有iterator接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的`Symbol.iterator`方法。 +ES6借鉴C++、Java、C#和Python语言,引入了`for...of`循环,作为遍历所有数据结构的统一的方法。一个数据结构只要部署了`Symbol.iterator`属性,就被视为具有iterator接口,就可以用`for...of`循环遍历它的成员。也就是说,`for...of`循环内部调用的是数据结构的`Symbol.iterator`方法。 for...of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象(比如arguments对象、DOM NodeList对象)、后文的Generator对象,以及字符串。 ### 数组 -数组原生具备iterator接口,for...of循环本质上就是调用这个接口产生的遍历器,可以用下面的代码证明。 +数组原生具备iterator接口,`for...of`循环本质上就是调用这个接口产生的遍历器,可以用下面的代码证明。 ```javascript - const arr = ['red', 'green', 'blue']; let iterator = arr[Symbol.iterator](); @@ -452,28 +451,24 @@ for(let v of arr) { for(let v of iterator) { console.log(v); // red green blue } - ``` -上面代码的for...of循环的两种写法是等价的。 +上面代码的`for...of`循环的两种写法是等价的。 -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循环,允许遍历获得键值。 +JavaScript原有的`for...in`循环,只能获得对象的键名,不能直接获取键值。ES6提供for...of循环,允许遍历获得键值。 ```javascript - var arr = ["a", "b", "c", "d"]; for (a in arr) { @@ -483,17 +478,15 @@ for (a in arr) { for (a of arr) { console.log(a); // a b c d } - ``` -上面代码表明,for...in循环读取键名,for...of循环读取键值。如果要通过for...of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法,参见《数组的扩展》章节。 +上面代码表明,`for...in`循环读取键名,`for...of`循环读取键值。如果要通过`for...of`循环,获取数组的索引,可以借助数组实例的`entries`方法和`keys`方法,参见《数组的扩展》章节。 ### Set和Map结构 -Set和Map结构也原生具有Iterator接口,可以直接使用for...of循环。 +Set和Map结构也原生具有Iterator接口,可以直接使用`for...of`循环。 ```javascript - var engines = Set(["Gecko", "Trident", "Webkit", "Webkit"]); for (var e of engines) { console.log(e); @@ -512,13 +505,11 @@ for (var [name, value] of es6) { // edition: 6 // committee: TC39 // standard: ECMA-262 - ``` 上面代码演示了如何遍历Set结构和Map结构。值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set结构遍历时,返回的是一个值,而Map结构遍历时,返回的是一个数组,该数组的两个成员分别为当前Map成员的键名和键值。 ```javascript - let map = new Map().set('a', 1).set('b', 2); for (let pair of map) { console.log(pair); @@ -535,16 +526,15 @@ for (let [key, value] of map) { ### 计算生成的数据结构 -有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6的数组、Set、Map都部署了以下三个方法,调用后都返回遍历器。 +有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6的数组、Set、Map都部署了以下三个方法,调用后都返回遍历器对象。 -- entries() 返回一个遍历器,用来遍历 [键名, 键值] 组成的数组。对于数组,键名就是索引值;对于Set,键名与键值相同。Map结构的iterator接口,默认就是调用entries方法。 -- keys() 返回一个遍历器,用来遍历所有的键名。 -- values() 返回一个遍历器,用来遍历所有的键值。 +- `entries()` 返回一个遍历器对象,用来遍历`[键名, 键值]`组成的数组。对于数组,键名就是索引值;对于Set,键名与键值相同。Map结构的iterator接口,默认就是调用entries方法。 +- `keys()` 返回一个遍历器对象,用来遍历所有的键名。 +- `values()` 返回一个遍历器对象,用来遍历所有的键值。 -这三个方法调用后生成的遍历器,所遍历的都是计算生成的数据结构。 +这三个方法调用后生成的遍历器对象,所遍历的都是计算生成的数据结构。 ```javascript - let arr = ['a', 'b', 'c']; for (let pair of arr.entries()) { console.log(pair); @@ -552,15 +542,13 @@ for (let pair of arr.entries()) { // [0, 'a'] // [1, 'b'] // [2, 'c'] - ``` ### 类似数组的对象 -类似数组的对象包括好几类。下面是for...of循环用于字符串、DOM NodeList对象、arguments对象的例子。 +类似数组的对象包括好几类。下面是`for...of`循环用于字符串、DOM NodeList对象、arguments对象的例子。 ```javascript - // 字符串 let str = "hello"; @@ -584,25 +572,21 @@ function printArgs() { printArgs('a', 'b'); // 'a' // 'b' - ``` -对于字符串来说,for...of循环还有一个特点,就是会正确识别32位UTF-16字符。 +对于字符串来说,`for...of`循环还有一个特点,就是会正确识别32位UTF-16字符。 ```javascript - for (let x of 'a\uD83D\uDC0A') { console.log(x); } // 'a' // '\uD83D\uDC0A' - ``` 并不是所有类似数组的对象都具有iterator接口,一个简便的解决方法,就是使用Array.from方法将其转为数组。 ```javascript - let arrayLike = { length: 2, 0: 'a', 1: 'b' }; // 报错 @@ -614,15 +598,13 @@ for (let x of arrayLike) { for (let x of Array.from(arrayLike)) { console.log(x); } - ``` ### 对象 -对于普通的对象,for...of结构不能直接使用,会报错,必须部署了iterator接口后才能使用。但是,这样情况下,for...in循环依然可以用来遍历键名。 +对于普通的对象,`for...of`结构不能直接使用,会报错,必须部署了iterator接口后才能使用。但是,这样情况下,`for...in`循环依然可以用来遍历键名。 ```javascript - var es6 = { edition: 6, committee: "TC39", @@ -640,10 +622,9 @@ for (e of es6) { console.log(e); } // TypeError: es6 is not iterable - ``` -上面代码表示,对于普通的对象,for...in循环可以遍历键名,for...of循环会报错。 +上面代码表示,对于普通的对象,`for...in`循环可以遍历键名,`for...of`循环会报错。 一种解决方法是,使用`Object.keys`方法将对象的键名生成一个数组,然后遍历这个数组。 @@ -653,7 +634,7 @@ for (var key of Object.keys(someObject)) { } ``` -在对象上部署iterator接口的代码,参见本章前面部分。一个方便的方法是将数组的`Symbol.iterator`属性,直接赋值给其他对象的`Symbol.iterator`属性。比如,想要让for...of循环遍历jQuery对象,只要加上下面这一行就可以了。 +在对象上部署iterator接口的代码,参见本章前面部分。一个方便的方法是将数组的`Symbol.iterator`属性,直接赋值给其他对象的`Symbol.iterator`属性。比如,想要让`for...of`环遍历jQuery对象,只要加上下面这一行就可以了。 ```javascript jQuery.prototype[Symbol.iterator] = @@ -695,9 +676,9 @@ myArray.forEach(function (value) { }); ``` -这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效。 +这种写法的问题在于,无法中途跳出`forEach`循环,break命令或return命令都不能奏效。 -for...in循环可以遍历数组的键名。 +`for...in`循环可以遍历数组的键名。 ```javascript for (var index in myArray) { @@ -713,9 +694,9 @@ for...in循环有几个缺点。 3)某些情况下,for...in循环会以任意顺序遍历键名。 -总之,for...in循环主要是为遍历对象而设计的,不适用于遍历数组。 +总之,`for...in`循环主要是为遍历对象而设计的,不适用于遍历数组。 -for...of循环相比上面几种做法,有一些显著的优点。 +`for...of`循环相比上面几种做法,有一些显著的优点。 ```javascript for (let value of myArray) { @@ -727,7 +708,7 @@ for (let value of myArray) { - 不同用于forEach方法,它可以与break、continue和return配合使用。 - 提供了遍历所有数据结构的统一操作接口。 -下面是一个使用break语句,跳出for...of循环的例子。 +下面是一个使用break语句,跳出`for...of`循环的例子。 ```javascript for (var n of fibonacci) { @@ -737,4 +718,4 @@ for (var n of fibonacci) { } ``` -上面的例子,会输出斐波纳契数列小于等于1000的项。如果当前项大于1000,就会使用break语句跳出for...of循环。 +上面的例子,会输出斐波纳契数列小于等于1000的项。如果当前项大于1000,就会使用break语句跳出`for...of`循环。 diff --git a/docs/module.md b/docs/module.md index 2af3f9e87..106dc8bd7 100644 --- a/docs/module.md +++ b/docs/module.md @@ -296,7 +296,7 @@ export { area as circleArea } from 'circle'; module math from "circleplus"; import exp from "circleplus"; -console.log(exp(math.PI)); +console.log(exp(math.E)); ``` 上面代码中的"import exp"表示,将circleplus模块的默认方法加载为exp方法。 From 9dcdc6a9e83aa571f3607f52d4322171e3214750 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 31 Aug 2015 11:12:16 +0800 Subject: [PATCH 0041/1267] edit module, array --- docs/array.md | 31 +++++++++++++++++++++++++++++-- docs/module.md | 47 +++++++++++++++++++++++++++++++++++++---------- docs/number.md | 21 +++++++++++++++++++++ docs/reference.md | 39 +++++++++++++++++++++++++-------------- docs/string.md | 15 +++++++++++---- 5 files changed, 123 insertions(+), 30 deletions(-) diff --git a/docs/array.md b/docs/array.md index 24b0da382..e693e617c 100644 --- a/docs/array.md +++ b/docs/array.md @@ -182,7 +182,7 @@ for (let [index, elem] of ['a', 'b'].entries()) { ## 数组实例的includes() -Array.protypeto.includes方法返回一个布尔值,表示某个数组是否包含给定的值。该方法属于ES7。 +`Array.protypeto.includes`方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的`includes`方法类似。该方法属于ES7,但Babel转码器已经支持。 ```javascript [1, 2, 3].includes(2); // true @@ -190,13 +190,35 @@ Array.protypeto.includes方法返回一个布尔值,表示某个数组是否 [1, 2, NaN].includes(NaN); // true ``` -该方法的第二个参数表示搜索的起始位置,默认为0。 +该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。 ```javascript [1, 2, 3].includes(3, 3); // false [1, 2, 3].includes(3, -1); // true ``` +没有该方法之前,我们通常使用数组的`indexOf`方法,检查是否包含某个值。 + +```javascript +if (arr.indexOf(el) !== -1) { + // ... +} +``` + +`indexOf`方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相当运算符(===)进行判断,这会导致对`NaN`的误判。 + +```javascript +[NaN].indexOf(NaN) +// -1 +``` + +`includes`使用的是不一样的判断算法,就没有这个问题。 + +```javascript +[NaN].includes(NaN) +// true +``` + 下面代码用来检查当前环境是否支持该方法,如果不支持,部署一个简易的替代版本。 ```javascript @@ -208,6 +230,11 @@ const contains = (() => contains(["foo", "bar"], "baz"); // => false ``` +另外,Map和Set数据结构有一个`has`方法,需要注意与`includes`区分。 + +- Map结构的`has`方法,是用来查找键名的,比如`Map.prototype.has(key)`、`WeakMap.prototype.has(key)`、`Reflect.has(target, propertyKey)`。 +- Set结构的`has`方法,是用来查找值的,比如`Set.prototype.has(value)`、`WeakSet.prototype.has(value)`。 + ## 数组推导 数组推导(array comprehension)提供简洁写法,允许直接通过现有数组生成新数组。这项功能本来是要放入ES6的,但是TC39委员会想继续完善这项功能,让其支持所有数据结构(内部调用iterator对象),不像现在只支持数组,所以就把它推迟到了ES7。Babel转码器已经支持这个功能。 diff --git a/docs/module.md b/docs/module.md index 106dc8bd7..ebcd2a556 100644 --- a/docs/module.md +++ b/docs/module.md @@ -20,11 +20,13 @@ import { stat, exists, readFile } from 'fs'; 所以,ES6可以在编译时就完成模块编译,效率要比CommonJS模块高。 +需要注意的是,ES6的模块自动采用严格模块,不管你有没有在模块头部加上`"use strict"`。 + ## export命令 -模块功能主要由两个命令构成:export和import。export命令用于用户自定义模块,规定对外接口;import命令用于输入其他模块提供的功能,同时创造命名空间(namespace),防止函数名冲突。 +模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。 -ES6允许将独立的JS文件作为模块,也就是说,允许一个JavaScript脚本文件调用另一个脚本文件。该文件内部的所有变量,外部无法获取,必须使用export关键字输出变量。下面是一个JS文件,里面使用export命令输出变量。 +一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个JS文件,里面使用export命令输出变量。 ```javascript // profile.js @@ -58,6 +60,23 @@ export function multiply (x, y) { 上面代码对外输出一个函数multiply。 +通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。 + +```javascript +function v1() { ... } +function v2() { ... } + +export { + v1 as streamV1, + v2 as streamV2, + v2 as streamLatestVersion +}; +``` + +上面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。 + +最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下面的import命令也是如此。 + ## import命令 使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。 @@ -72,9 +91,9 @@ function setName(element) { } ``` -上面代码属于另一个文件main.js,import命令就用于加载profile.js文件,并从中输入变量。import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。 +上面代码的import命令,就用于加载profile.js文件,并从中输入变量。import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。 -如果想为输入的变量重新取一个名字,import语句中要使用as关键字,将输入的变量重命名。 +如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。 ```javascript import { lastName as surname } from './profile'; @@ -129,7 +148,7 @@ export function circumference(radius) { ```javascript // main.js -import { area, circumference } from 'circle'; +import { area, circumference } from './circle'; console.log("圆面积:" + area(4)); console.log("圆周长:" + circumference(14)); @@ -138,7 +157,7 @@ console.log("圆周长:" + circumference(14)); 上面写法是逐一指定要输入的方法。另一种写法是整体输入。 ```javascript -import * as circle from 'circle'; +import * as circle from './circle'; console.log("圆面积:" + circle.area(4)); console.log("圆周长:" + circle.circumference(14)); @@ -151,7 +170,7 @@ module命令可以取代import语句,达到整体输入模块的作用。 ```javascript // main.js -module circle from 'circle'; +module circle from './circle'; console.log("圆面积:" + circle.area(4)); console.log("圆周长:" + circle.circumference(14)); @@ -161,7 +180,7 @@ module命令后面跟一个变量,表示输入的模块定义在该变量上 ## export default命令 -从前面的例子可以看出,使用import的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。 +从前面的例子可以看出,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。 为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到`export default`命令,为模块指定默认输出。 @@ -182,7 +201,7 @@ import customName from './export-default'; customName(); // 'foo' ``` -上面代码的import命令,可以用任意名称指向`export-default.js`输出的方法。需要注意的是,这时import命令后面,不使用大括号。 +上面代码的import命令,可以用任意名称指向`export-default.js`输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。 export default命令用在非匿名函数前,也是可以的。 @@ -226,8 +245,16 @@ export function crc32(){}; export default function (x, y) { return x * y; }; + +// 或者 + +function add(x, y) { + return x * y; +}; +export {add as default}; + // app.js -import { default } from 'modules'; +import { default as xxx } from 'modules'; ``` 有了`export default`命令,输入模块时就非常直观了,以输入jQuery模块为例。 diff --git a/docs/number.md b/docs/number.md index ea7ff8b28..4225cc110 100644 --- a/docs/number.md +++ b/docs/number.md @@ -430,3 +430,24 @@ ES6新增了6个三角函数方法。 - Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine) - Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine) - Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent) + +## 指数运算符 + +ES7新增了一个指数运算符(`**`),目前Babel转码器已经支持。 + +```javascript +2 ** 2 // 4 +2 ** 3 // 8 +``` + +指数运算符可以与等号结合,形成一个新的赋值运算符(`**=`)。 + +```javascript +let a = 2; +a **= 2; +// 等同于 a = a * a; + +let b = 3; +b **= 3; +// 等同于 b = b * b * b; +``` diff --git a/docs/reference.md b/docs/reference.md index 6c2b2dd69..9b6c60ad9 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -26,26 +26,15 @@ - 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/) -## 语法点 +## let和const - 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/) -- 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新增的数组方法的全面介绍 -- Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 -- Ragan Wald, [Destructuring and Recursion in ES6](http://raganwald.com/2015/02/02/destructuring.html): rest参数和扩展运算符的详细介绍 -## Collections +## 解构赋值 -- 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结构的详细介绍 -- Jason Orendorff, [ES6 In Depth: Collections](https://hacks.mozilla.org/2015/06/es6-in-depth-collections/):Set和Map结构的设计思想 -- Axel Rauschmayer, [Converting ES6 Maps to and from JSON](http://www.2ality.com/2015/08/es6-map-json.html): 如何将Map与其他数据结构互相转换 +- Nick Fitzgerald, [Destructuring Assignment in ECMAScript 6](http://fitzgeraldnick.com/weblog/50/): 详细介绍解构赋值的用法 ## 字符串 @@ -59,6 +48,19 @@ - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 - Axel Rauschmayer, [New regular expression features in ECMAScript 6](http://www.2ality.com/2015/07/regexp-es6.html):ES6正则特性的详细介绍 +## 数组 + +- Axel Rauschmayer, [ECMAScript 6’s new array methods](http://www.2ality.com/2014/05/es6-array-methods.html): 对ES6新增的数组方法的全面介绍 +- TC39, [Array.prototype.includes](https://github.com/tc39/Array.prototype.includes/): 数组的includes方法的规格 + +## 函数 + +- 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/) +- Axel Rauschmayer, [Handling required parameters in ECMAScript 6](http://www.2ality.com/2014/04/required-parameters-es6.html) +- Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 +- Ragan Wald, [Destructuring and Recursion in ES6](http://raganwald.com/2015/02/02/destructuring.html): rest参数和扩展运算符的详细介绍 + ## 对象 - Addy Osmani, [Data-binding Revolutions with Object.observe()](http://www.html5rocks.com/en/tutorials/es7/observe/): 介绍Object.observe()的概念 @@ -81,6 +83,14 @@ - Jason Orendorff, [ES6 In Depth: Symbols](https://hacks.mozilla.org/2015/06/es6-in-depth-symbols/) - Keith Cirkel, [Metaprogramming in ES6: Symbols and why they're awesome](http://blog.keithcirkel.co.uk/metaprogramming-in-es6-symbols/): Symbol的深入介绍 +## Set和Map + +- 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结构的详细介绍 +- Jason Orendorff, [ES6 In Depth: Collections](https://hacks.mozilla.org/2015/06/es6-in-depth-collections/):Set和Map结构的设计思想 +- Axel Rauschmayer, [Converting ES6 Maps to and from JSON](http://www.2ality.com/2015/08/es6-map-json.html): 如何将Map与其他数据结构互相转换 + ## Iterator - Mozilla Developer Network, [Iterators and generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) @@ -138,6 +148,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模块的静态化设计思想 +- Jason Orendorff, [ES6 In Depth: Modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/): ES6模块设计思想的介绍 ## 工具 diff --git a/docs/string.md b/docs/string.md index fb6533606..b1e575aeb 100644 --- a/docs/string.md +++ b/docs/string.md @@ -286,14 +286,14 @@ function authorize(user, action) { var x = 1; var y = 2; -console.log(`${x} + ${y} = ${x+y}`) +`${x} + ${y} = ${x + y}` // "1 + 2 = 3" -console.log(`${x} + ${y*2} = ${x+y*2}`) +`${x} + ${y * 2} = ${x + y * 2}` // "1 + 4 = 5" var obj = {x: 1, y: 2}; -console.log(`${obj.x + obj.y}`) +`${obj.x + obj.y}` // 3 ``` @@ -304,7 +304,7 @@ function fn() { return "Hello World"; } -console.log(`foo ${fn()} bar`); +`foo ${fn()} bar` // foo Hello World bar ``` @@ -318,6 +318,13 @@ var msg = `Hello, ${place}`; // 报错 ``` +由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果表达式放在引号之中,将会原样输出。 + +```javascript +`Hello ${'World'}` +// "Hello World" +``` + ## 标签模板 模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。 From 4a3412ce2303f218db7801f41c06f8f96fbf9afd Mon Sep 17 00:00:00 2001 From: Lunfu Zhong Date: Tue, 1 Sep 2015 15:41:41 +0800 Subject: [PATCH 0042/1267] Fixed code block typo --- docs/intro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.md b/docs/intro.md index ca24edcaa..f0d91e829 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -223,7 +223,7 @@ $ browserify script.js -t babelify --outfile bundle.js ] } } -`` +``` ## Traceur转码器 From 0012c4f1eb3f2729b010fed959a197ec68d8b489 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 2 Sep 2015 09:00:16 +0800 Subject: [PATCH 0043/1267] edit function/spread --- docs/array.md | 42 +++++++++++++++---- docs/function.md | 102 +++++++++++++++++++++++----------------------- docs/generator.md | 42 ++++++++++--------- docs/module.md | 2 +- 4 files changed, 110 insertions(+), 78 deletions(-) diff --git a/docs/array.md b/docs/array.md index e693e617c..5e3ec7e7a 100644 --- a/docs/array.md +++ b/docs/array.md @@ -5,8 +5,16 @@ Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。 ```javascript -let ps = document.querySelectorAll('p'); +Array.from('hello') +// ['h', 'e', 'l', 'l', 'o'] + +Array.from([1, 2, 3]) +// [1, 2, 3] +let namesSet = new Set(['a', 'b']) +Array.from(namesSet) // ['a', 'b'] + +let ps = document.querySelectorAll('p'); Array.from(ps).forEach(function (p) { console.log(p); }); @@ -45,6 +53,9 @@ Array.from()还可以接受第二个参数,作用类似于数组的map方法 Array.from(arrayLike, x => x * x); // 等同于 Array.from(arrayLike).map(x => x * x); + +Array.from([1, 2, 3], (x) => x * x) +// [1, 4, 9] ``` 下面的例子将数组中布尔值为false的成员转为0。 @@ -54,7 +65,16 @@ Array.from([1, , 2, , 3], (n) => n || 0) // [1, 0, 2, 0, 3] ``` -Array.from()的一个应用是,将字符串转为数组,然后返回字符串的长度。这样可以避免JavaScript将大于\uFFFF的Unicode字符,算作两个字符的bug。 +`Array.from()`可以将各种值转为真正的数组,并且还提供map功能。这实际上意味着,你可以在数组里造出任何想要的值。 + +```javascript +Array.from({ length: 2 }, () => 'jack') +// ['jack', 'jack'] +``` + +上面代码中,`Array.from`的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。 + +`Array.from()`的另一个应用是,将字符串转为数组,然后返回字符串的长度。这样可以避免JavaScript将大于`\uFFFF`的Unicode字符,算作两个字符的bug。 ```javascript function countSymbols(string) { @@ -95,8 +115,8 @@ function ArrayOf(){ 数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。 ```javascript -var found = [1, 4, -5, 10].find((n) => n < 0); -console.log("found:", found); +[1, 4, -5, 10].find((n) => n < 0) +// -5 ``` 上面代码找出数组中第一个小于0的成员。 @@ -129,7 +149,7 @@ console.log("found:", found); // 0 ``` -上面代码中,indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。 +上面代码中,`indexOf`方法无法识别数组的NaN成员,但是`findIndex`方法可以借助`Object.is`方法做到。 ## 数组实例的fill() @@ -156,10 +176,9 @@ fill()还可以接受第二个和第三个参数,用于指定填充的起始 ## 数组实例的entries(),keys()和values() -ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。 +ES6提供三个新的方法——`entries()`,`keys()`和`values()`——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用`for...of`循环进行遍历,唯一的区别是`keys()`是对键名的遍历、`values()`是对键值的遍历,`entries()`是对键值对的遍历。 ```javascript - for (let index of ['a', 'b'].keys()) { console.log(index); } @@ -177,7 +196,16 @@ for (let [index, elem] of ['a', 'b'].entries()) { } // 0 "a" // 1 "b" +``` + +如果不使用`for...of`循环,可以手动调用遍历器对象的`next`方法,进行遍历。 +```javascript +let letter = ['a', 'b', 'c']; +let entries = letter.entries(); +console.log(entries.next().value); // [0, 'a'] +console.log(entries.next().value); // [1, 'b'] +console.log(entries.next().value); // [2, 'c'] ``` ## 数组实例的includes() diff --git a/docs/function.md b/docs/function.md index 9cf38e671..26ef1f5a3 100644 --- a/docs/function.md +++ b/docs/function.md @@ -34,7 +34,6 @@ if (arguments.length === 1) { ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。 ```javascript - function log(x, y = 'World') { console.log(x, y); } @@ -42,13 +41,11 @@ function log(x, y = 'World') { log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello - ``` 可以看到,ES6的写法比ES5简洁许多,而且非常自然。下面是另一个例子。 ```javascript - function Point(x = 0, y = 0) { this.x = x; this.y = y; @@ -56,7 +53,6 @@ function Point(x = 0, y = 0) { var p = new Point(); // p = { x:0, y:0 } - ``` 除了简洁,ES6的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本彻底拿到这个参数,也不会导致以前的代码无法运行。 @@ -64,11 +60,9 @@ var p = new Point(); 默认值的写法非常灵活,下面是一个为对象属性设置默认值的例子。 ```javascript - fetch(url, { body = '', method = 'GET', headers = {} }){ console.log(method); } - ``` 上面代码中,传入函数fetch的第二个参数是一个对象,调用的时候可以为它的三个属性设置默认值。 @@ -76,11 +70,9 @@ fetch(url, { body = '', method = 'GET', headers = {} }){ 甚至还可以设置双重默认值。 ```javascript - fetch(url, { method = 'GET' } = {}){ console.log(method); } - ``` 上面代码中,调用函数fetch时,如果不含第二个参数,则默认值为一个空对象;如果包含第二个参数,则它的method属性默认值为GET。 @@ -88,7 +80,6 @@ fetch(url, { method = 'GET' } = {}){ 定义了默认值的参数,必须是函数的尾部参数,其后不能再有其他无默认值的参数。这是因为有了默认值以后,该参数可以省略,只有位于尾部,才可能判断出到底省略了哪些参数。 ```javascript - // 以下两种写法都是错的 function f(x = 5, y) { @@ -96,20 +87,17 @@ 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,就没有触发默认值。 @@ -117,11 +105,9 @@ foo(undefined, null) 指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。 ```javascript - (function(a){}).length // 1 (function(a = 5){}).length // 0 (function(a, b, c = 5){}).length // 2 - ``` 上面代码中,length属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。 @@ -129,7 +115,6 @@ foo(undefined, null) 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。 ```javascript - function throwIfMissing() { throw new Error('Missing parameter'); } @@ -140,7 +125,6 @@ function foo(mustBeProvided = throwIfMissing()) { foo() // Error: Missing parameter - ``` 上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。 @@ -222,7 +206,6 @@ const sortNumbers = (...numbers) => numbers.sort(); rest参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用rest参数改写数组push方法的例子。 ```javascript - function push(array, ...items) { items.forEach(function(item) { array.push(item); @@ -232,33 +215,41 @@ function push(array, ...items) { var a = []; push(a, 1, 2, 3) - ``` 注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。 ```javascript - // 报错 function f(a, ...b, c) { // ... } - ``` 函数的length属性,不包括rest参数。 ```javascript - (function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1 - ``` ## 扩展运算符 -扩展运算符(spread)是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。该运算符主要用于函数调用。 +扩展运算符(spread)是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。 + +```javascript +console.log(...[1, 2, 3]) +// 1 2 3 + +console.log(1, ...[2, 3, 4], 5) +// 1 2 3 4 5 + +[...document.querySelectorAll('div')] +// <- [
,
,
] +``` + +该运算符主要用于函数调用。 ```javascript function push(array, ...items) { @@ -275,12 +266,6 @@ add(...numbers) // 42 上面代码中,`array.push(...items)`和`add(...numbers)`这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。 -下面是Date函数的参数使用扩展运算符的例子。 - -```javascript -const date = new Date(...[2015, 1, 1]); -``` - 由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。 ```javascript @@ -295,14 +280,6 @@ 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 @@ -316,7 +293,7 @@ Math.max(...[14, 3, 77]) Math.max(14, 3, 77); ``` -上面代码表示,由于JavaScript不提供求数组最大元素的函数,所以只能套用Math.max函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用Math.max了。 +上面代码表示,由于JavaScript不提供求数组最大元素的函数,所以只能套用`Math.max`函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用`Math.max`了。 另一个例子是通过push函数,将一个数组添加到另一个数组的尾部。 @@ -334,22 +311,36 @@ arr1.push(...arr2); 上面代码的ES5写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。有了扩展运算符,就可以直接将数组传入push方法。 -扩展运算符还可以用于数组的赋值。 +扩展运算符与正常的函数参数可以结合使用,非常灵活。 ```javascript -var a = [1]; -var b = [2, 3, 4]; -var c = [6, 7]; -var d = [0, ...a, ...b, 5, ...c]; - -d -// [0, 1, 2, 3, 4, 5, 6, 7] +function f(v, w, x, y, z) { } +var args = [0, 1]; +f(-1, ...args, 2, ...[3]); ``` -上面代码其实也提供了,将一个数组拷贝进另一个数组的便捷方法。 +扩展运算符可以简化很多种ES5的写法。 ```javascript -const arr2 = [...arr1]; +// ES5 +[1, 2].concat(more) +// ES6 +[1, 2, ...more] + +// ES5 +list.push.apply(list, [3, 4]) +// ES6 +list.push(...[3, 4]) + +// ES5 +a = list[0], rest = list.slice(1) +// ES6 +[a, ...rest] = list + +// ES5 +new (Date.bind.apply(Date, [null, 2015, 1, 1])) +// ES6 +new Date(...[2015, 1, 1]); ``` 扩展运算符也可以与解构赋值结合起来,用于生成数组。 @@ -425,7 +416,6 @@ let arr = [...map.keys()]; // [1, 2, 3] Generator函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。 - ```javascript var go = function*(){ yield 1; @@ -569,7 +559,19 @@ var handler = { }; ``` -上面代码的init方法中,使用了箭头函数,这导致this绑定handler对象,否则回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。 +上面代码的init方法中,使用了箭头函数,这导致this绑定handler对象,否则回调函数运行时,`this.doSomething`这一行会报错,因为此时this指向全局对象。 + +```javascript +function Timer () { + this.seconds = 0 + setInterval(() => this.seconds++, 1000) +} +var timer = new Timer() +setTimeout(() => console.log(timer.seconds), 3100) +// 3 +``` + +上面代码中,`Timer`函数内部的`setInterval`调用了`this.seconds`属性,通过箭头函数将`this`绑定在Timer的实例对象。否则,输出结果是0,而不是3。 由于this在箭头函数中被绑定,所以不能用call()、apply()、bind()这些方法去改变this的指向。 diff --git a/docs/generator.md b/docs/generator.md index 56cb8af23..63d4e868f 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -6,9 +6,11 @@ Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。本章详细介绍Generator函数的语法和API,它的异步编程应用请看《异步操作》一章。 -Generator函数有多种理解角度。从语法上,首先可以把它理解成一个函数的内部状态的遍历器(也就是说,Generator函数是一个状态机)。它每调用一次,就进入下一个内部状态。Generator函数可以控制内部状态的变化,依次遍历这些状态。 +Generator函数有多种理解角度。从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。 -形式上,Generator函数是一个普通函数,但是有两个特征。一是,function命令与函数名之间有一个星号;二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态(yield语句在英语里的意思就是“产出”)。 +执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。 + +形式上,Generator函数是一个普通函数,但是有两个特征。一是,`function`命令与函数名之间有一个星号;二是,函数体内部使用`yield`语句,定义不同的内部状态(yield语句在英语里的意思就是“产出”)。 ```javascript function* helloWorldGenerator() { @@ -20,11 +22,11 @@ function* helloWorldGenerator() { var hw = helloWorldGenerator(); ``` -上面代码定义了一个Generator函数helloWorldGenerator,它内部有两个yield语句“hello”和“world”,即该函数有三个状态:hello,world和return语句(结束执行)。 +上面代码定义了一个Generator函数`helloWorldGenerator`,它内部有两个yield语句“hello”和“world”,即该函数有三个状态:hello,world和return语句(结束执行)。 然后,Generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。 -下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句(或return语句)为止。换言之,Generator函数是分段执行的,yield命令是暂停执行的标记,而next方法可以恢复执行。 +下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用`next`方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个`yield`语句(或`return`语句)为止。换言之,Generator函数是分段执行的,`yield`语句是暂停执行的标记,而`next`方法可以恢复执行。 ```javascript hw.next() @@ -40,33 +42,33 @@ hw.next() // { value: undefined, done: true } ``` -上面代码一共调用了四次next方法。 +上面代码一共调用了四次`next`方法。 -第一次调用,Generator函数开始执行,直到遇到第一个yield语句为止。next方法返回一个对象,它的value属性就是当前yield语句的值hello,done属性的值false,表示遍历还没有结束。 +第一次调用,Generator函数开始执行,直到遇到第一个`yield`语句为止。`next`方法返回一个对象,它的`value`属性就是当前`yield`语句的值hello,`done`属性的值false,表示遍历还没有结束。 -第二次调用,Generator函数从上次yield语句停下的地方,一直执行到下一个yield语句。next方法返回的对象的value属性就是当前yield语句的值world,done属性的值false,表示遍历还没有结束。 +第二次调用,Generator函数从上次`yield`语句停下的地方,一直执行到下一个`yield`语句。`next`方法返回的对象的`value`属性就是当前`yield`语句的值world,`done`属性的值false,表示遍历还没有结束。 -第三次调用,Generator函数从上次yield语句停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。 +第三次调用,Generator函数从上次`yield`语句停下的地方,一直执行到`return`语句(如果没有return语句,就执行到函数结束)。`next`方法返回的对象的`value`属性,就是紧跟在`return`语句后面的表达式的值(如果没有`return`语句,则`value`属性的值为undefined),`done`属性的值true,表示遍历已经结束。 -第四次调用,此时Generator函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。 +第四次调用,此时Generator函数已经运行完毕,`next`方法返回对象的`value`属性为undefined,`done`属性为true。以后再调用`next`方法,返回的都是这个值。 -总结一下,调用Generator函数,返回一个部署了Iterator接口的遍历器对象,用来操作内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。 +总结一下,调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的`next`方法,就会返回一个有着`value`和`done`两个属性的对象。`value`属性表示当前的内部状态的值,是`yield`语句后面那个表达式的值;`done`属性是一个布尔值,表示是否遍历结束。 ### yield语句 -由于Generator函数返回的遍历器,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志。 +由于Generator函数返回的遍历器对象,只有调用`next`方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。`yield`语句就是暂停标志。 -遍历器next方法的运行逻辑如下。 +遍历器对象的`next`方法的运行逻辑如下。 -(1)遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。 +(1)遇到`yield`语句,就暂停执行后面的操作,并将紧跟在`yield`后面的那个表达式的值,作为返回的对象的`value`属性值。 -(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。 +(2)下一次调用`next`方法时,再继续往下执行,直到遇到下一个`yield`语句。 -(3)如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。 +(3)如果没有再遇到新的`yield`语句,就一直运行到函数结束,直到`return`语句为止,并将`return`语句后面的表达式的值,作为返回的对象的`value`属性值。 -(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。 +(4)如果该函数没有`return`语句,则返回的对象的`value`属性值为`undefined`。 -需要注意的是,yield语句后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。 +需要注意的是,`yield`语句后面的表达式,只有当调用`next`方法、内部指针指向该语句时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。 ```javascript function* gen{ @@ -74,11 +76,11 @@ function* gen{ } ``` -上面代码中,yield后面的表达式`123 + 456`,不会立即求值,只会在next方法将指针移到这一句时,才会求值。 +上面代码中,yield后面的表达式`123 + 456`,不会立即求值,只会在`next`方法将指针移到这一句时,才会求值。 -yield语句与return语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说Generator生成了一系列的值,这也就是它的名称的来历(在英语中,generator这个词是“生成器”的意思)。 +`yield`语句与`return`语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到`yield`,函数暂停执行,下一次再从该位置继续向后执行,而`return`语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)`return`语句,但是可以执行多次(或者说多个)`yield`语句。正常函数只能返回一个值,因为只能执行一次`return`;Generator函数可以返回一系列的值,因为可以有任意多个`yield`。从另一个角度看,也可以说Generator生成了一系列的值,这也就是它的名称的来历(在英语中,generator这个词是“生成器”的意思)。 -Generator函数可以不用yield语句,这时就变成了一个单纯的暂缓执行函数。 +Generator函数可以不用`yield`语句,这时就变成了一个单纯的暂缓执行函数。 ```javascript function* f() { diff --git a/docs/module.md b/docs/module.md index ebcd2a556..aef2fb0cc 100644 --- a/docs/module.md +++ b/docs/module.md @@ -20,7 +20,7 @@ import { stat, exists, readFile } from 'fs'; 所以,ES6可以在编译时就完成模块编译,效率要比CommonJS模块高。 -需要注意的是,ES6的模块自动采用严格模块,不管你有没有在模块头部加上`"use strict"`。 +需要注意的是,ES6的模块自动采用严格模式,不管你有没有在模块头部加上`"use strict"`。 ## export命令 From ae9f668caac171d621b85c948618639c5b396d81 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 4 Sep 2015 09:13:45 +0800 Subject: [PATCH 0044/1267] edit module --- docs/module.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/module.md b/docs/module.md index aef2fb0cc..6deacfb43 100644 --- a/docs/module.md +++ b/docs/module.md @@ -9,16 +9,18 @@ ES6的Class只是面向对象编程的语法糖,升级了ES5的构造函数的 ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。比如,CommonJS模块就是对象,输入时必须查找对象属性。 ```javascript -var { stat, exists, readFile } = require('fs'); +let { stat, exists, readFile } = require('fs'); ``` +上面代码的实质是整体加载`fs`模块(即加载`fs`的所有方法),然后使用时用到3个方法。这种加载称为“运行时加载”。 + ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式。 ```javascript import { stat, exists, readFile } from 'fs'; ``` -所以,ES6可以在编译时就完成模块编译,效率要比CommonJS模块高。 +上面代码的实质是从`fs`模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”,即ES6可以在编译时就完成模块编译,效率要比CommonJS模块的加载方式高。 需要注意的是,ES6的模块自动采用严格模式,不管你有没有在模块头部加上`"use strict"`。 From 1b550c7627004937fe92540f0dce2a7a14c70d7b Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 5 Sep 2015 10:46:59 +0800 Subject: [PATCH 0045/1267] edit let --- docs/class.md | 2 +- docs/let.md | 67 ++++++++++++++++++++++++++++++++++++++++++----- docs/object.md | 17 ++++++++++++ docs/reference.md | 1 + 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/docs/class.md b/docs/class.md index 3032c59ec..bcca32970 100644 --- a/docs/class.md +++ b/docs/class.md @@ -39,7 +39,7 @@ class Point { 上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5的构造函数Point,对应ES6的Point类的构造方法。 -Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。 +Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。 ES6的类,完全可以看作构造函数的另一种写法。 diff --git a/docs/let.md b/docs/let.md index 5dde6c74f..941023e8b 100644 --- a/docs/let.md +++ b/docs/let.md @@ -55,7 +55,7 @@ a[6](); // 6 ### 不存在变量提升 -let不像var那样,会发生“变量提升”现象。 +`let`不像`var`那样,会发生“变量提升”现象。 ```javascript function do_something() { @@ -64,9 +64,9 @@ function do_something() { } ``` -上面代码在声明foo之前,就使用这个变量,结果会抛出一个错误。 +上面代码在声明`foo`之前,就使用这个变量,结果会抛出一个错误。 -这也意味着typeof不再是一个百分之百安全的操作。 +这也意味着`typeof`不再是一个百分之百安全的操作。 ```javascript if (1) { @@ -75,7 +75,9 @@ if (1) { } ``` -上面代码中,由于块级作用域内typeof运行时,x还没有值,所以会抛出一个ReferenceError。 +上面代码中,由于块级作用域内typeof运行时,x还没有值,所以会抛出一个`ReferenceError`。 + +### 暂时性死区 只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。 @@ -137,7 +139,6 @@ bar(); 上面代码中,函数bar的参数func,默认是一个匿名函数,返回值为变量foo。这个匿名函数的作用域就不是bar。这个匿名函数声明时,是处在外层作用域,所以内部的foo指向函数体外的声明,输出outer。它实际上等同于下面的代码。 - ```javascript let foo = 'outer'; let f = x => foo; @@ -150,6 +151,10 @@ function bar(func = f) { bar(); ``` +ES6规定暂时性死区和不存在变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在ES5是很常见的,现在有了这种规定,避免此类错误就很容易了。 + +总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。 + ### 不允许重复声明 let不允许在相同作用域内,重复声明同一个变量。 @@ -235,6 +240,31 @@ function f1() { 上面的函数有两个代码块,都声明了变量n,运行后输出5。这表示外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10。 +ES6允许块级作用域的任意嵌套。 + +```javascript +{{{{{let insane = 'Hello World'}}}}}; +insane // "Hello World" +``` + +上面代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。 + +```javascript +{{{{ + {let insane = 'Hello World'} + console.log(insane); // 报错 +}}}}; +``` + +内层作用域可以定义外层作用域的同名变量。 + +```javascript +{{{{ + let insane = 'Hello World'; + {let insane = 'Hello World';} +}}}}; +``` + 块级作用域的出现,实际上使得获得广泛应用的立即执行匿名函数(IIFE)不再必要了。 ```javascript @@ -267,6 +297,29 @@ function f() { console.log('I am outside!'); } 上面代码在ES5中运行,会得到“I am inside!”,但是在ES6中运行,会得到“I am outside!”。这是因为ES5存在函数提升,不管会不会进入if代码块,函数声明都会提升到当前作用域的顶部,得到执行;而ES6支持块级作用域,不管会不会进入if代码块,其内部声明的函数皆不会影响到作用域的外部。 +```javascript +{ + let a = 'secret'; + function f() { + return a; + } +} +f() // 报错 +``` + +上面代码中,块级作用域外部,无法调用块级作用域内部定义的函数。如果确实需要调用,就要像下面这样处理。 + +```javascript +let f; +{ + let a = 'secret'; + f = function () { + return a; + } +} +f() // "secret" +``` + 需要注意的是,如果在严格模式下,函数只能在顶层作用域和函数内声明,其他情况(比如if代码块、循环代码块)的声明都会报错。 ## const命令 @@ -293,10 +346,10 @@ if (true) { const MAX = 5; } -// 常量MAX在此处不可得 +MAX // Uncaught ReferenceError: MAX is not defined ``` -const命令也不存在提升,只能在声明的位置后面使用。 +const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。 ```javascript if (true) { diff --git a/docs/object.md b/docs/object.md index f5fc3c70c..487f8cdd1 100644 --- a/docs/object.md +++ b/docs/object.md @@ -64,6 +64,23 @@ getPoint() // {x:1, y:10} ``` +赋值器和取值器,也可以采用简洁写法。 + +```javascript +var cart = { + _wheels: 4, + get wheels () { + return this._wheels + }, + set wheels (value) { + if (value < this._wheels) { + throw new Error('hey, come back here!') + } + this._wheels = value + } +} +``` + ## 属性名表达式 JavaScript语言定义对象的属性,有两种方法。 diff --git a/docs/reference.md b/docs/reference.md index 9b6c60ad9..a3acd823f 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -31,6 +31,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的行为 +- Nicolas Bevacqua, [ES6 Let, Const and the “Temporal Dead Zone” (TDZ) in Depth](http://ponyfoo.com/articles/es6-let-const-and-temporal-dead-zone-in-depth) ## 解构赋值 From cd9fac819e438874bf19f4cba2254effa8ad0936 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 5 Sep 2015 10:50:37 +0800 Subject: [PATCH 0046/1267] edit let --- docs/let.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/let.md b/docs/let.md index 941023e8b..de2c96e58 100644 --- a/docs/let.md +++ b/docs/let.md @@ -75,11 +75,11 @@ if (1) { } ``` -上面代码中,由于块级作用域内typeof运行时,x还没有值,所以会抛出一个`ReferenceError`。 +上面代码中,由于块级作用域内`typeof`运行时,`x`还没有值,所以会抛出一个`ReferenceError`。 ### 暂时性死区 -只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。 +只要块级作用域内存在`let`命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。 ```javascript var tmp = 123; @@ -90,9 +90,9 @@ if (true) { } ``` -上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。 +上面代码中,存在全局变量`tmp`,但是块级作用域内`let`又声明了一个局部变量`tmp`,导致后者绑定这个块级作用域,所以在`let`声明变量前,对`tmp`赋值会报错。 -ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些命令,就会报错。 +ES6明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些命令,就会报错。 总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。 @@ -110,7 +110,7 @@ if (true) { } ``` -上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。 +上面代码中,在`let`命令声明变量`tmp`之前,都属于变量`tmp`的“死区”。 有些“死区”比较隐蔽,不太容易发现。 @@ -122,7 +122,7 @@ function bar(x = y, y = 2) { bar(); // 报错 ``` -上面代码中,调用bar函数之所以报错,是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。 +上面代码中,调用`bar`函数之所以报错,是因为参数`x`默认值等于另一个参数`y`,而此时`y`还没有声明,属于”死区“。 需要注意的是,函数的作用域是其声明时所在的作用域。如果函数A的参数是函数B,那么函数B的作用域不是函数A。 @@ -137,7 +137,7 @@ function bar(func = x => foo) { bar(); ``` -上面代码中,函数bar的参数func,默认是一个匿名函数,返回值为变量foo。这个匿名函数的作用域就不是bar。这个匿名函数声明时,是处在外层作用域,所以内部的foo指向函数体外的声明,输出outer。它实际上等同于下面的代码。 +上面代码中,函数`bar`的参数`func`,默认是一个匿名函数,返回值为变量`foo`。这个匿名函数的作用域就不是`bar`。这个匿名函数声明时,是处在外层作用域,所以内部的`foo`指向函数体外的声明,输出`outer`。它实际上等同于下面的代码。 ```javascript let foo = 'outer'; From ed5f62d589a98dea0e978b2326f081c5bc78b770 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 6 Sep 2015 17:17:13 +0800 Subject: [PATCH 0047/1267] =?UTF-8?q?jquery=E6=94=B9=E4=B8=BA=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E6=9C=AC=E5=9C=B0=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 5 ++--- js/jquery-1.11.0.min.js | 4 ++++ js/jquery-ui.min.js | 7 +++++++ 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 js/jquery-1.11.0.min.js create mode 100644 js/jquery-ui.min.js diff --git a/index.html b/index.html index f19f923cd..8e41d3565 100644 --- a/index.html +++ b/index.html @@ -4,9 +4,8 @@ - - - + + diff --git a/js/jquery-1.11.0.min.js b/js/jquery-1.11.0.min.js new file mode 100644 index 000000000..73f33fb3a --- /dev/null +++ b/js/jquery-1.11.0.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f +}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("