From 6bf087c771378862d4c54e3e4b330227ff98c7d0 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Tue, 16 Jun 2015 15:33:36 +0800 Subject: [PATCH] edit set --- docs/function.md | 4 ---- docs/generator.md | 59 +++++++++++++++++++++++++---------------------- docs/let.md | 12 +--------- docs/set-map.md | 50 +++++++++++++++++++++------------------ docs/string.md | 16 +------------ 5 files changed, 60 insertions(+), 81 deletions(-) diff --git a/docs/function.md b/docs/function.md index 315d32f..82059c1 100644 --- a/docs/function.md +++ b/docs/function.md @@ -413,10 +413,8 @@ const [first, ...middle, last] = [1, 2, 3, 4, 5]; JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。 ```javascript - var dateFields = readDateFields(database); var d = new Date(...dateFields); - ``` 上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date。 @@ -424,10 +422,8 @@ var d = new Date(...dateFields); 除了展开数组,扩展运算符还可以将一个数值扩展成数组。 ```javascript - [...5] // [0, 1, 2, 3, 4, 5] - ``` 扩展运算符还可以将字符串转为真正的数组。 diff --git a/docs/generator.md b/docs/generator.md index 04436e5..fd8a7cf 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -7,7 +7,6 @@ 在形式上,Generator是一个普通函数,但是有两个特征。一是,function命令与函数名之间有一个星号;二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态(yield语句在英语里的意思就是“产出”)。 ```javascript - function* helloWorldGenerator() { yield 'hello'; yield 'world'; @@ -15,7 +14,6 @@ function* helloWorldGenerator() { } var hw = helloWorldGenerator(); - ``` 上面代码定义了一个Generator函数helloWorldGenerator,它的遍历器有两个成员“hello”和“world”。调用这个函数,就会得到遍历器。 @@ -127,7 +125,6 @@ for (var f of flat(arr)){ 上面代码也会产生句法错误,因为forEach方法的参数是一个普通函数,但是在里面使用了yield语句。一种修改方法是改用for循环。 ```javascript - var arr = [1, [[2, 3], 4], [5, 6]]; var flat = function* (a){ @@ -146,7 +143,6 @@ for (var f of flat(arr)){ console.log(f); } // 1, 2, 3, 4, 5, 6 - ``` ## next方法的参数 @@ -154,7 +150,6 @@ for (var f of flat(arr)){ yield语句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。 ```javascript - function* f() { for(var i=0; true; i++) { var reset = yield i; @@ -167,7 +162,6 @@ var g = f(); g.next() // { value: 0, done: false } g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false } - ``` 上面代码先定义了一个可以无限运行的Generator函数f,如果next方法没有参数,每次运行到yield语句,变量reset的值总是undefined。当next方法带一个参数true时,当前的变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。 @@ -177,7 +171,24 @@ g.next(true) // { value: 0, done: false } 再看一个例子。 ```javascript +function* foo(x) { + var y = 2 * (yield (x + 1)); + var z = yield (y / 3); + return (x + y + z); +} +var a = foo(5); + +a.next() // Object{value:6, done:false} +a.next() // Object{value:NaN, done:false} +a.next() // Object{value:NaN, done:false} +``` + +上面代码中,第二次运行next方法的时候不带参数,导致y的值等于`2 * undefined`(即NaN),除以3以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于`5 + NaN + undefined`,即NaN。 + +如果向next方法提供参数,返回结果就完全不一样了。 + +```javascript function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); @@ -192,7 +203,6 @@ it.next(12) // { value:8, done:false } it.next(13) // { value:42, done:true } - ``` 上面代码第一次调用next方法时,返回`x+1`的值6;第二次调用next方法,将上一次yield语句的值设为12,因此y等于24,返回`y / 3`的值8;第三次调用next方法,将上一次yield语句的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。 @@ -249,15 +259,12 @@ for (let n of fibonacci()) { Generator函数还有一个特点,它可以在函数体外抛出错误,然后在函数体内捕获。 ```javascript - var g = function* () { while (true) { try { yield; } catch (e) { - if (e != 'a') { - throw e; - } + if (e != 'a') throw e; console.log('内部捕获', e); } } @@ -274,7 +281,6 @@ try { } // 内部捕获 a // 外部捕获 b - ``` 上面代码中,遍历器i连续抛出两个错误。第一个错误被Generator函数体内的catch捕获,然后Generator函数执行完成,于是第二个错误被函数体外的catch捕获。 @@ -287,9 +293,7 @@ var g = function* () { try { yield; } catch (e) { - if (e != 'a') { - throw e; - } + if (e != 'a') throw e; console.log('内部捕获', e); } } @@ -436,15 +440,15 @@ try { 上面代码中,第二个next方法向函数体内传入一个参数42,数值是没有toUpperCase方法的,所以会抛出一个TypeError错误,被函数体外的catch捕获。 -一旦Generator执行过程中抛出错误,就不会再执行下去了。如果此后还调用next方法,将一直返回发生错误前的那个值。 +一旦Generator执行过程中抛出错误,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefined、done属性等于true的对象,即JavaScript引擎认为这个Generator已经运行结束了。 ```javascript - function* g() { yield 1; console.log('throwing an exception'); throw new Error('generator broke!'); yield 2; + yield 3; } function log(generator) { @@ -452,36 +456,35 @@ function log(generator) { console.log('starting generator'); try { v = generator.next(); - console.log('got back', v); + console.log('第一次运行next方法', v); } catch (err) { - console.log('fixing generator', v); + console.log('捕捉错误', v); } try { v = generator.next(); - console.log('got back', v); + console.log('第二次运行next方法', v); } catch (err) { - console.log('fixing generator', v); + console.log('捕捉错误', v); } try { v = generator.next(); - console.log('got back', v); + console.log('第三次运行next方法', v); } catch (err) { - console.log('fixing generator', v); + console.log('捕捉错误', v); } console.log('caller done'); } log(g()); // starting generator -// got back { value: 1, done: false } +// 第一次运行next方法 { value: 1, done: false } // throwing an exception -// fixing generator { value: 1, done: false } -// fixing generator { value: 1, done: false } +// 捕捉错误 { value: 1, done: false } +// 第三次运行next方法 { value: undefined, done: true } // caller done - ``` -上面代码在Generator函数g抛出错误以后,再调用next方法,就不再执行下去了,一直停留在上一次的状态。 +上面代码一共三次运行next方法,第二次运行的时候会抛出错误,然后第三次运行的时候,Generator函数就已经结束了,不再执行下去了。 ## yield*语句 diff --git a/docs/let.md b/docs/let.md index 6d00096..1bb0407 100644 --- a/docs/let.md +++ b/docs/let.md @@ -7,7 +7,6 @@ ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。 ```javascript - { let a = 10; var b = 1; @@ -15,7 +14,6 @@ ES6新增了let命令,用来声明变量。它的用法类似于var,但是 a // ReferenceError: a is not defined. b // 1 - ``` 上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。 @@ -23,12 +21,10 @@ b // 1 for循环的计数器,就很合适使用let命令。 ```javascript - for(let i = 0; i < arr.length; i++){} console.log(i) //ReferenceError: i is not defined - ``` 上面代码的计数器i,只在for循环体内有效。 @@ -36,7 +32,6 @@ console.log(i) 下面的代码如果使用var,最后输出的是10。 ```javascript - var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { @@ -44,13 +39,11 @@ for (var i = 0; i < 10; i++) { }; } a[6](); // 10 - ``` 如果使用let,声明的变量仅在块级作用域内有效,最后输出的是6。 ```javascript - var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { @@ -58,7 +51,6 @@ for (let i = 0; i < 10; i++) { }; } a[6](); // 6 - ``` ### 不存在变量提升 @@ -370,12 +362,11 @@ var constantize = (obj) => { ## 全局对象的属性 -全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。ES5规定,所有全局变量都是全局对象的属性。 +全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。在JavaScript语言中,所有全局变量都是全局对象的属性。 ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。 ```javascript - var a = 1; // 如果在node环境,可以写成global.a // 或者采用通用方法,写成this.a @@ -383,7 +374,6 @@ window.a // 1 let b = 1; window.b // undefined - ``` 上面代码中,全局变量a由var命令声明,所以它是全局对象的属性;全局变量b由let命令声明,所以它不是全局对象的属性,返回undefined。 diff --git a/docs/set-map.md b/docs/set-map.md index a3ed696..5766558 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -64,7 +64,7 @@ Set数据结构有以下方法。 ```javascript -s.add(1).add(2).add(2); +s.add(1).add(2).add(2); // 注意2被加入了两次 s.size // 2 @@ -165,19 +165,32 @@ for (let x of set) { ``` -Set结构的forEach方法,用于对每个成员执行某种操作,返回修改后的Set结构。 +Set结构的forEach方法,用于对每个成员执行某种操作,不影响原来的Set结构。 ```javascript - let set = new Set([1, 2, 3]); - -set.forEach((value, key) => value*2 ) -// 返回Set结构{2, 4, 6} - +set.forEach((value, key) => value * 2 ) +// set的值还是1, 2, 3 ``` 上面代码说明,forEach方法的参数就是一个处理函数。该函数的参数依次为键值、键名、集合本身(上例省略了该参数)。另外,forEach方法还可以有第二个参数,表示绑定的this对象。 +如果想在遍历操作中,同步改变原来的Set结构,目前没有直接的方法,但有两种变通方法。一种是利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构;另一种是利用Array.from方法。 + +```javascript +// 方法一 +let set = new Set([1, 2, 3]); +set = new Set([...set].map(val => val * 2)); +// set的值是2, 4, 6 + +// 方法二 +let set = new Set([1, 2, 3]); +set = new Set(Array.from(set, val => val * 2)); +// set的值是2, 4, 6 +``` + +上面代码提供了两种方法,直接在遍历操作中改变原来的Set结构。 + 为了与Map结构保持一致,Set结构也有keys和entries方法,这时每个值的键名就是键值。 ```javascript @@ -204,7 +217,7 @@ for ( let [key, value] of set.entries() ){ ```javascript let set = new Set(['red', 'green', 'blue']); -let arr = [...set]; +let arr = [...set]; // ['red', 'green', 'blue'] ``` @@ -293,7 +306,6 @@ WeakSet结构有以下三个方法。 下面是一个例子。 ```javascript - var ws = new WeakSet(); var obj = {}; var foo = {}; @@ -304,26 +316,18 @@ ws.add(obj); ws.has(window); // true ws.has(foo); // false -ws.delete(window); +ws.delete(window); ws.has(window); // false - -ws.clear(); - ``` WeakSet没有size属性,没有办法遍历它的成员。 ```javascript - -ws.size -// undefined +ws.size // undefined +ws.forEach // undefined ws.forEach(function(item){ console.log('WeakSet has ' + item)}) // TypeError: undefined is not a function - -ws.forEach -// undefined - ``` 上面代码试图获取size和forEach属性,结果都不能成功。 @@ -418,7 +422,7 @@ map.get(k2) // 222 ```javascript let map = new Map(); - + map.set(NaN, 123); map.get(NaN) // 123 @@ -492,12 +496,12 @@ m.get("edition") // 6 let map = new Map(); map.set('foo', true); map.set('bar', false); - + map.size // 2 map.clear() map.size // 0 -``` +``` **(3)遍历** diff --git a/docs/string.md b/docs/string.md index 50769ba..9e5b5eb 100644 --- a/docs/string.md +++ b/docs/string.md @@ -304,10 +304,9 @@ repeat()返回一个新字符串,表示将原字符串重复n次。 ## 正则表达式的y修饰符 -除了u修饰符,ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。它的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始,不同之处在于,g修饰符只确保剩余位置中存在匹配,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。 +除了u修饰符,ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。它的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始,不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。 ```javascript - var s = "aaa_aa_a"; var r1 = /a+/g; var r2 = /a+/y; @@ -317,7 +316,6 @@ r2.exec(s) // ["aaa"] r1.exec(s) // ["aa"] r2.exec(s) // null - ``` 上面代码有两个正则表达式,一个使用g修饰符,另一个使用y修饰符。这两个正则表达式各执行了两次,第一次执行的时候,两者行为相同,剩余字符串都是“_aa_a”。由于g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。 @@ -325,13 +323,11 @@ r2.exec(s) // null 如果改一下正则表达式,保证每次都能头部匹配,y修饰符就会返回结果了。 ```javascript - var s = "aaa_aa_a"; var r = /a+_/y; r.exec(s) // ["aaa_"] r.exec(s) // ["aa_"] - ``` 上面代码每次匹配,都是从剩余字符串的头部开始。 @@ -339,10 +335,8 @@ r.exec(s) // ["aa_"] 进一步说,y修饰符号隐含了头部匹配的标志ˆ。 ```javascript - /b/y.exec("aba") // null - ``` 上面代码由于不能保证头部匹配,所以返回null。y修饰符的设计本意,就是让头部匹配的标志ˆ在全局匹配中都有效。 @@ -350,10 +344,8 @@ r.exec(s) // ["aa_"] 与y修饰符相匹配,ES6的正则对象多了sticky属性,表示是否设置了y修饰符。 ```javascript - var r = /hello\d/y; r.sticky // true - ``` ## Regexp.escape() @@ -663,20 +655,16 @@ HelloWorldApp.main(); 模板处理函数的第一个参数,还有一个raw属性。它也是一个数组,成员与处理函数的第一个参数完全一致,唯一的区别是字符串被转义前的原始格式,这是为了模板函数处理的方便而提供的。 ```javascript - tag`First line\nSecond line` - ``` 上面代码中,tag函数的第一个参数是一个数组`["First line\nSecond line"]`,这个数组有一个raw属性,等于`["First line\\nSecond line"]`,两者唯一的区别就是斜杠被转义了。 ```javascript - function tag(strings) { console.log(strings.raw[0]); // "First line\\nSecond line" } - ``` ## String.raw() @@ -684,13 +672,11 @@ function tag(strings) { String.raw方法,往往用来充当模板字符串的处理函数,返回字符串被转义前的原始格式。 ```javascript - String.raw`Hi\n${2+3}!`; // "Hi\\n5!" String.raw`Hi\u000A!`; // 'Hi\\u000A!' - ``` String.raw方法也可以正常的函数形式使用。这时,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组。