diff --git a/docs/array.md b/docs/array.md index e693e61..5e3ec7e 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 9cf38e6..26ef1f5 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 56cb8af..63d4e86 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 ebcd2a5..aef2fb0 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命令