This commit is contained in:
Ruan Yifeng
2015-06-16 15:33:36 +08:00
parent f69843a8f6
commit 6bf087c771
5 changed files with 60 additions and 81 deletions
-4
View File
@@ -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]
```
扩展运算符还可以将字符串转为真正的数组。
+31 -28
View File
@@ -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*语句
+1 -11
View File
@@ -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。
+27 -23
View File
@@ -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)遍历**
+1 -15
View File
@@ -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修饰符号隐含了头部匹配的标志&#710;。
```javascript
/b/y.exec("aba")
// null
```
上面代码由于不能保证头部匹配,所以返回null。y修饰符的设计本意,就是让头部匹配的标志&#710;在全局匹配中都有效。
@@ -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属性的值应该是一个数组。