数组的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值
es5写法
let a = 1 ;
let b = 2 ;
let c = 3 ;
es6写法
let [a , b, c] = [1 , 2 , 3 ]
let [foo, [[bar], baz]] = [1 , [[2 ], 3 ]]
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo" , "bar" , "baz" ]
third // "baz"
let [x, , y] = [1 , 2 , 3 ]
x // 1
y // 3
let [head, ...tail] = [1 , 2 , 3 , 4 ]
head // 1
tail // [2 , 3 , 4 ]
let [x, y, ...z] = ['a ']
x // "a"
y // undefined //解构不成功,变量的值就等于undefined
z // []
不完全解构:即等号左边的模式,只匹配一部分的等号右边的数组
let [x, y] = [1 , 2 , 3 ];
x
y
let [a, [b], d] = [1 , [2 , 3 ], 4 ];
a
b
d
解构的大前提在于必须具备 Iterator 接口
默认值:解构赋值允许指定默认值
let [foo = true ] = [];
foo // true
let [x, y = 'b' ] = ['a' ]; // x='a' , y='b'
let [x, y = 'b' ] = ['a' , undefined ]; // x='a' , y='b'
// ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined ,默认值是不会生效的
let [x = 1 ] = [undefined ];
x // 1
let [x = 1 ] = [null ];
x // null
对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
let { foo, bar } = { foo: "aaa" , bar: "bbb" };
foo
bar
let { bar, foo } = { foo: "aaa" , bar: "bbb" };
foo
bar
let { baz } = { foo: "aaa" , bar: "bbb" };
baz
变量名与属性名不一致,必须写成下面这样
var { foo: baz } = { foo: 'aaa' , bar: 'bbb' };
baz
let obj = { first: 'hello' , last: 'world' };
let { first: f, last: l } = obj;
f
l
let { foo: baz } = { foo: "aaa" , bar: "bbb" };
baz
foo
采用这种写法时,变量的声明和赋值是一体的。对于let和const来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。
let foo;
let {foo} = {foo: 1 };
let baz;
let {bar: baz} = {bar: 1 };
let foo;
({foo} = {foo: 1 });
let baz;
({bar: baz} = {bar: 1 });
对象的解构也可以指定默认值。对象的属性值严格等于undefined
var {x = 3 } = {};
x
var {x, y = 5 } = {x: 1 };
x
y
var {x:y = 3 } = {};
y
var {x:y = 3 } = {x: 5 };
y
var { message: msg = 'Something went wrong' } = {};
msg
可以对现有对象赋值
let { log , sin , cos } = Math;
数组本质是特殊的对象,因此可以对数组进行对象属性的解构
let arr = [1 , 2 , 3 ];
let {0 : first, [arr.length - 1 ] : last} = arr;
first
last
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello' ;
a
b
c
d
e
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len } = 'hello' ;
len
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象
let {toString: s} = 123 ;
s === Number .prototype.toString
let {toString: s} = true ;
s === Boolean .prototype.toString
只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined ;
let { prop: y } = null ;
函数参数的解构赋值
函数的参数也可以使用解构赋值
function add ([x, y]) {
return x + y;
}
add([1 , 2 ]); // 3
[[1, 2], [3, 4]] .map(([a, b]) => a + b);
// [ 3 , 7 ]
函数参数的解构也可以使用默认值
function move ({x = 0 , y = 0 } = {}) {
return [x, y];
}
move ({x: 3 , y: 8 });
move ({x: 3 });
move ({});
move ();
写法不同,结果不一样
function move ({x, y} = { x: 0 , y: 0 }) {
return [x, y];
}
move ({x: 3 , y: 8 });
move ({x: 3 });
move ({});
move ();
圆括号问题
解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。
由此带来的问题是,如果模式中出现圆括号怎么处理。ES6的规则是,只要有可能导致解构的歧义,就不得使用圆括号。
但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。
变量声明语句中,不能带有圆括号。
let [(a)] = [1 ];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};
let { o: ({ p: p }) } = { o: { p: 2 } };
函数参数中,模式不能带有圆括号,函数参数也属于变量声明
function f ([(z) ]) { return z; }
赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中。
// 全部报错
({ p: a }) = { p: 42 };
([a]) = [5];
[({ p: a }), { x: c }] = [{}, {}];
可以使用圆括号的情况
[(b)] = [3 ];
({ p: (d) } = {});
[(parseInt .prop)] = [3 ];
用途
(1)交换变量的值
let x = 1 ;
let y = 2 ;
[x, y] = [y, x];
(2)从函数返回多个值:函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便
function example () {
return [1 , 2 , 3 ];
}
let [a, b, c] = example();
function example () {
return {
foo: 1 ,
bar: 2
};
}
let { foo, bar } = example();
(3)函数参数的定义:解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1 , 2 , 3 ]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3 , y: 2 , x: 1 });
(4)提取JSON数据:解构赋值对提取JSON对象中的数据,尤其有用
let jsonData = {
id: 42 ,
status: "OK" ,
data : [867, 5309]
};
let { id, status, data : number } = jsonData;
console.log(id, status, number);
// 42 , "OK" , [867 , 5309 ]
(5)函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
//指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo' ;这样的语句
(6)遍历Map结构:任何部署了Iterator接口的对象,都可以用for...of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。
var map = new Map();
map.set ('first' , 'hello' );
map.set ('second' , 'world' );
for (let [key, value ] of map) {
console.log(key + " is " + value );
}