1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 18:32:22 +00:00

edit docs/destructuring

This commit is contained in:
ruanyf 2015-10-29 11:53:22 +08:00
parent 7bad020cde
commit ee5962043e
3 changed files with 190 additions and 63 deletions

View File

@ -370,7 +370,7 @@ function thunkify(fn){
};
```
它的源码主要多了一个检查机制变量called确保回调函数只运行一次。这样的设计与下文的Generator函数相关。请看下面的例子。
它的源码主要多了一个检查机制,变量`called`确保回调函数只运行一次。这样的设计与下文的Generator函数相关。请看下面的例子。
```javascript
function f(a, b, callback){
@ -380,11 +380,12 @@ function f(a, b, callback){
}
var ft = thunkify(f);
ft(1, 2)(console.log);
var print = console.log.bind(console);
ft(1, 2)(print);
// 3
```
上面代码中由于thunkify只允许回调函数执行一次所以只输出一行结果。
上面代码中,由于`thunkify`只允许回调函数执行一次,所以只输出一行结果。
### Generator 函数的流程管理

View File

@ -2,6 +2,8 @@
## 数组的解构赋值
### 基本用法
ES6允许按照一定模式从数组和对象中提取值对变量进行赋值这被称为解构Destructuring
以前,为变量赋值,只能直接指定值。
@ -28,7 +30,7 @@ foo // 1
bar // 2
baz // 3
let [,,third] = ["foo", "bar", "baz"];
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
@ -38,16 +40,21 @@ y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
```
如果解构不成功变量的值就等于undefined。
如果解构不成功,变量的值就等于`undefined`
```javascript
var [foo] = [];
var [bar, foo] = [1];
```
以上两种情况都属于解构不成功foo的值都会等于undefined。
以上两种情况都属于解构不成功,`foo`的值都会等于`undefined`
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
@ -73,29 +80,10 @@ let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
```
解构赋值允许指定默认值。
```javascript
var [foo = true] = [];
foo // true
[x, y='b'] = ['a'] // x='a', y='b'
[x, y='b'] = ['a', undefined] // x='a', y='b'
```
注意ES6内部使用严格相等运算符===判断一个位置是否有值。所以如果一个数组成员不严格等于undefined默认值是不会生效的。
```javascript
var [x = 1] = [undefined];
x // 1
var [x = 1] = [null];
x // null
```
上面代码中如果一个数组成员是null默认值就不会生效因为null不严格等于undefined。
上面的表达式都会报错因为等号右边的值要么转为对象以后不具备Iterator接口前五个表达式要么本身就不具备Iterator接口最后一个表达式
解构赋值不仅适用于var命令也适用于let和const命令。
@ -130,6 +118,62 @@ sixth // 5
上面代码中,`fibs`是一个Generator函数原生具有Iterator接口。解构赋值会依次从这个接口获取值。
### 默认值
解构赋值允许指定默认值。
```javascript
var [foo = true] = [];
foo // true
[x, y = 'b'] = ['a'] // x='a', y='b'
[x, y = 'b'] = ['a', undefined] // x='a', y='b'
```
注意ES6内部使用严格相等运算符`===`),判断一个位置是否有值。所以,如果一个数组成员不严格等于`undefined`,默认值是不会生效的。
```javascript
var [x = 1] = [undefined];
x // 1
var [x = 1] = [null];
x // null
```
上面代码中,如果一个数组成员是`null`,默认值就不会生效,因为`null`不严格等于`undefined`
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
```javascript
function f(){
console.log('aaa');
}
let [x = f()] = [1];
```
上面代码中,因为`x`能取到值,所以函数`f`根本不会执行。上面的代码其实等价于下面的代码。
```javascript
let x;
if ([1][0] === undefined) {
x = f();
} else {
x = [1][0];
}
```
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
```javascript
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=1; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError
```
上面最后一个表达式之所以会报错,是因为`x`用到默认值`y`时,`y`还没有声明。
## 对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
@ -181,6 +225,26 @@ foo // error: foo is not defined
上面代码中,真正被赋值的是变量`baz`,而不是模式`foo`
注意采用这种写法时变量的声明和赋值是一体的。对于let和const来说变量不能重新声明所以一旦赋值的变量以前声明过就会报错。
```javascript
let foo;
let {foo} = {foo: 1}; // SyntaxError: Duplicate declaration "foo"
let baz;
let {bar: baz} = {bar: 1}; // SyntaxError: Duplicate declaration "baz"
```
上面代码中,解构赋值的变量都会重新声明,所以报错了。不过,因为`var`命令允许重新声明,所以这个错误只会在使用`let``const`命令时出现。如果没有第二个let命令上面的代码就不会报错。
```javascript
let foo;
({foo} = {foo: 1}); // 成功
let baz;
({bar: baz} = {bar: 1}); // 成功
```
和数组一样,解构也可以用于嵌套结构的对象。
```javascript
@ -216,6 +280,18 @@ start // error: start is undefined
上面代码中,只有`line`是变量,`loc``start`都是模式,不会被赋值。
下面是嵌套赋值的例子。
```javascript
let obj = {};
let arr = [];
({ foo: obj.prop, bar: arr[0] }) = { foo: 123, bar: true };
obj // {prop:123}
arr // [true]
```
对象的解构也可以指定默认值。
```javascript
@ -223,10 +299,11 @@ var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
console.log(x, y) // 1, 5
x // 1
y // 5
var { message: msg = "Something went wrong" } = {};
console.log(msg); // "Something went wrong"
msg // "Something went wrong"
```
默认值生效的条件是,对象的属性值严格等于`undefined`
@ -276,18 +353,28 @@ var x;
```javascript
// 正确的写法
({x} = {x:1});
({x} = {x: 1});
```
上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。关于圆括号与解构赋值的关系,参见下文。
解构赋值允许,等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。
```javascript
({} = [true, false]);
({} = 'abc');
({} = []);
```
上面的表达式虽然毫无意义,但是语法是合法的,可以执行。
对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
```javascript
let { log, sin, cos } = Math;
```
上面代码将Math对象的对数、正弦、余弦三个方法赋值到对应的变量上使用起来就会方便很多。
上面代码将`Math`对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。
## 字符串的解构赋值
@ -309,9 +396,30 @@ let {length : len} = 'hello';
len // 5
```
## 数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
```javascript
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
```
上面代码中,数值和布尔值的包装对象都有`toString`属性,因此变量`s`都能取到值。
解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。由于`undefined``null`无法转为对象,所以对它们进行解构赋值,都会报错。
```javascript
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
```
## 函数参数的解构赋值
函数的参数也可以使用解构。
函数的参数也可以使用解构赋值
```javascript
function add([x, y]){
@ -321,7 +429,7 @@ function add([x, y]){
add([1, 2]) // 3
```
上面代码中函数add的参数实际上不是一个数组而是通过解构得到的变量x和y。
上面代码中函数add的参数实际上不是一个数组而是通过解构得到的变量`x``y`
函数参数的解构也可以使用默认值。
@ -336,7 +444,7 @@ move({}); // [0, 0]
move(); // [0, 0]
```
上面代码中函数move的参数是一个对象通过对这个对象进行解构得到变量x和y的值。如果解构失败x和y等于默认值。
上面代码中,函数`move`的参数是一个对象,通过对这个对象进行解构,得到变量`x``y`的值。如果解构失败,`x``y`等于默认值。
注意,指定函数参数的默认值时,不能采用下面的写法。
@ -421,9 +529,7 @@ function f([(z)]) { return z; }
**1交换变量的值**
```javascript
[x, y] = [y, x];
```
上面代码交换变量x和y的值这样的写法不仅简洁而且易读语义非常清晰。
@ -433,7 +539,6 @@ function f([(z)]) { return z; }
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
```javascript
// 返回一个数组
function example() {
@ -450,7 +555,6 @@ function example() {
};
}
var { foo, bar } = example();
```
**3函数参数的定义**
@ -458,15 +562,13 @@ var { foo, bar } = example();
解构赋值可以方便地将一组参数与变量名对应起来。
```javascript
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3])
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({x:1, y:2, z:3})
f({z: 3, y: 2, x: 1})
```
**4提取JSON数据**
@ -510,10 +612,9 @@ jQuery.ajax = function (url, {
**6遍历Map结构**
任何部署了Iterator接口的对象都可以用for...of循环遍历。Map结构原生支持Iterator接口配合变量的解构赋值获取键名和键值就非常方便。
任何部署了Iterator接口的对象都可以用`for...of`循环遍历。Map结构原生支持Iterator接口配合变量的解构赋值获取键名和键值就非常方便。
```javascript
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
@ -523,13 +624,11 @@ for (let [key, value] of map) {
}
// first is hello
// second is world
```
如果只想获取键名,或者只想获取键值,可以写成下面这样。
```javascript
// 获取键名
for (let [key] of map) {
// ...
@ -539,7 +638,6 @@ for (let [key] of map) {
for (let [,value] of map) {
// ...
}
```
**7输入模块的指定方法**

View File

@ -118,9 +118,16 @@ function bar(x = y, y = 2) {
bar(); // 报错
```
上面代码中,调用`bar`函数之所以报错,是因为参数`x`默认值等于另一个参数`y`,而此时`y`还没有声明,属于”死区“。
上面代码中,调用`bar`函数之所以报错,是因为参数`x`默认值等于另一个参数`y`,而此时`y`还没有声明,属于”死区“。如果`y`的默认值是`x`,就不会报错,因为此时`x`已经声明了。
需要注意的是函数的作用域是其声明时所在的作用域。如果函数A的参数是函数B那么函数B的作用域不是函数A。
```javascript
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]
```
需要注意的是,函数的作用域是其声明时所在的作用域。如果函数`A`的参数是函数`B`,那么函数`B`的作用域不是函数`A`
```javascript
let foo = 'outer';
@ -222,7 +229,7 @@ console.log(i); // 5
### ES6的块级作用域
let实际上为JavaScript新增了块级作用域。
`let`实际上为JavaScript新增了块级作用域。
```javascript
function f1() {
@ -234,7 +241,7 @@ function f1() {
}
```
上面的函数有两个代码块都声明了变量n运行后输出5。这表示外层代码块不受内层代码块的影响。如果使用var定义变量n最后输出的值就是10。
上面的函数有两个代码块,都声明了变量`n`运行后输出5。这表示外层代码块不受内层代码块的影响。如果使用`var`定义变量`n`最后输出的值就是10。
ES6允许块级作用域的任意嵌套。
@ -290,7 +297,7 @@ function f() { console.log('I am outside!'); }
}());
```
上面代码在ES5中运行会得到“I am inside!”但是在ES6中运行会得到“I am outside!”。这是因为ES5存在函数提升不管会不会进入if代码块函数声明都会提升到当前作用域的顶部得到执行而ES6支持块级作用域不管会不会进入if代码块其内部声明的函数皆不会影响到作用域的外部。
上面代码在ES5中运行会得到“I am inside!”但是在ES6中运行会得到“I am outside!”。这是因为ES5存在函数提升不管会不会进入 `if`代码块函数声明都会提升到当前作用域的顶部得到执行而ES6支持块级作用域不管会不会进入if代码块其内部声明的函数皆不会影响到作用域的外部。
```javascript
{
@ -326,10 +333,19 @@ const PI = 3.1415;
PI // 3.1415
PI = 3;
PI // 3.1415
// TypeError: "PI" is read-only
```
上面代码表明改变常量的值是不起作用的。需要注意的是,对常量重新赋值不会报错,只会默默地失败。
上面代码表明改变常量的值会报错。
const声明的变量不得改变值这意味着const一旦声明变量就必须立即初始化不能留到以后赋值。
```javascript
const foo;
// SyntaxError: missing = in const declaration
```
上面代码表示对于const来说只声明不赋值就会报错。
const的作用域与let命令相同只在声明所在的块级作用域内有效。
@ -350,9 +366,9 @@ if (true) {
}
```
上面代码在常量MAX声明之前就调用结果报错。
上面代码在常量`MAX`声明之前就调用,结果报错。
const声明的常量也与let一样不可重复声明。
const声明的常量也与`let`一样不可重复声明。
```javascript
var message = "Hello!";
@ -363,7 +379,7 @@ const message = "Goodbye!";
const age = 30;
```
由于const命令只是指向变量所在的地址,所以将一个对象声明为常量必须非常小心。
对于复合类型的变量变量名不指向数据而是指向数据所在的地址。const命令只是保证变量名指向的地址不变并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。
```javascript
const foo = {};
@ -372,10 +388,10 @@ foo.prop = 123;
foo.prop
// 123
foo = {} // 不起作用
foo = {} // TypeError: "foo" is read-only不起作用
```
上面代码中常量foo储存的是一个地址这个地址指向一个对象。不可变的只是这个地址即不能把foo指向另一个地址但对象本身是可变的所以依然可以为其添加新属性。
上面代码中,常量`foo`储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把`foo`指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
下面是另一个例子。
@ -386,16 +402,16 @@ a.length = 0; // 可执行
a = ["Dave"]; // 报错
```
上面代码中常量a是一个数组这个数组本身是可写的但是如果将另一个数组赋值给a就会报错。
上面代码中,常量`a`是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给`a`,就会报错。
如果真的想将对象冻结应该使用Object.freeze方法。
如果真的想将对象冻结,应该使用`Object.freeze`方法。
```javascript
const foo = Object.freeze({});
foo.prop = 123; // 不起作用
```
上面代码中常量foo指向一个冻结的对象所以添加新属性不起作用。
上面代码中,常量`foo`指向一个冻结的对象,所以添加新属性不起作用。
除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。
@ -410,6 +426,8 @@ var constantize = (obj) => {
};
```
ES5只有两种声明变量的方法var命令和function命令。ES6除了添加let和const命令后面章节还会提到另外两种声明变量的方法import命令和class命令。所以ES6一共有6种声明变量的方法。
## 跨模块常量
上面说过const声明的常量只在当前代码块有效。如果想设置跨模块的常量可以采用下面的写法。
@ -433,9 +451,19 @@ console.log(B); // 3
## 全局对象的属性
全局对象是最顶层的对象,在浏览器环境指的是window对象在Node.js指的是global对象。在JavaScript语言中所有全局变量都是全局对象的属性。Node的情况比较特殊这一条只对REPL环境适用模块环境必须显式声明成`global`的属性。)
全局对象是最顶层的对象,在浏览器环境指的是`window`在Node.js指的是`global`对象。ES5之中全局对象的属性与全局变量是等价的。
ES6规定var命令和function命令声明的全局变量属于全局对象的属性let命令、const命令、class命令声明的全局变量不属于全局对象的属性。
```javascript
window.a = 1;
a // 1
a = 2;
window.a // 2
```
上面代码中全局对象的属性赋值与全局变量的赋值是同一件事。对于Node来说这一条只对REPL环境适用模块环境之中全局变量必须显式声明成`global`对象的属性。)
这种规定被视为JavaScript语言的一大问题因为很容易不知不觉就创建了全局变量。ES6为了改变这一点一方面规定var命令和function命令声明的全局变量依旧是全局对象的属性另一方面规定let命令、const命令、class命令声明的全局变量不属于全局对象的属性。
```javascript
var a = 1;