ES6允许按照一定模式,从数组和对象(以及字符串、数值、布尔值、函数参数等)中提取值,按照对应位置给变量进行赋值,这被称为解构赋值。首先,假定你已经了解了什么是解构赋值,我们先来快速看一下它的常见用途。
1. 交换变量的值
let x=1,y=2;
[x,y]=[y,x];
与传统的引入中间变量进行值交换的方式相比,这种写法不仅简洁,而且易读,语义非常清晰
2. 解析函数多返回值
JS中函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回,通过解构赋值,我们可以很方便的将这些值取出来并赋给其它变量。
//返回一个数组
function arr(){
return [1,2,3];
}
let [x,y,z]=arr();
//返回一个对象
function obj(){
return {
foo:1,
bar:2
}
}
let {foo,bar}=obj();
3. 定义函数多参数传入方式
解构赋值可以很方便地将一组实参与形参变量名对应起来。
//参数是一组有序的值
function f([x,y,z]){...}
f([1,2,3]);
//参数是一组无序的值
function f({x,y,z}){...}
f({y:2,x:1,z:3});
4. 提取JSON数据
解构赋值对提取JSON对象中数据,尤其有用。
let jsonData={
id:20,
status:'OK',
data:[12,34]
};
let {id,status,data:number}=jsonData;
console.log(id,status,number);//20 'OK' [12,34]
5. 遍历部署了Iterator接口的数据结构时,即在for ... of
循环遍历自身每一项时,可以直接多参数传值。Map结构原生支持Iterator接口,使用变量的解构赋值,可以很方便的获取键名和键值。
var map=new Map();
map.set('first','hello');
map.set('second','world');
for(let item of map){
console.log(item);
}
for(let [key,value] of map){
console.log(key+'is'+value);
}
for(let [i,j] of map){
console.log(i+' is '+j);//结果同上,这里可以将每项item看做数组,所以可以用自定义变量获取对应位置的值
}
如果只想获取键名,或者只想获取键值,可以写成下面这样
//获取键名
for (let [key] of map){...}
//获取键值
for (let [,value] of map){...}
6.获取模块的指定方法
加载模块后,往往需要再次指定变量获取模块的某些方法。解构赋值直接精简了这一过程。
const {SourceMap,SourceNode} = require('source-map');
接下来详细介绍Destructuring assignment.
1. 数组的解构赋值
以前,为变量赋值,只能直接指定值
let a=1,b=2,c=3;
现在ES6允许这样
let [a,b,c]=[1,2,3]
看起来和golang的多参数赋值差不多,这里的代码实际上表示从数组中提取值,按照对应位置赋给变量。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
let [foo,[[bar],bar]]=[1,[[2],3]];
let [,,third]=['foo','bar','baz']
let [x,,y]=[1,2,3]
let [head,...tail]=[1,2,3,4];
let [x,y,...z]=['a'];
如果解构不成功,变量的值将等于undefined。若等号右边的值不是可遍历的数据结构(没有Iterator接口),那么将会报错
//报错
let [foo]=1;
let [foo]=undefined;
let [foo]=null;
let [foo]=false;
let [foo]=NaN;
let [foo]={};//注意,普通对象本身不具备Iterator接口,不可遍历
//for循环遍历,数组和对象都可使用for...in遍历,其中数组遍历时传入的参数值是索引,对象传入的是属性,数组还可以使用forEach方法进行遍历
对于Set结构,也可以使用数组的解构赋值
let [x,y,z]=new Set(['a','b','c']);
重点:事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
function* fibs(){
let a=0,b=1;
while(true){
yield a;
[a,b]=[b,a+b];
}
}
let [first,second,third,fourth,fifth,sixth]=fibs();
console.log(sixth);//5
上面代码中,fibs是一个Generator函数,原生具有Iterator接口,解构赋值会依次从这个接口获取值。
解构赋值允许指定默认值,只有当数组成员严格等于undefined
时,默认值才会生效。如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f(){...}
let [x=f()]=[1];
上面的代码中,因为x能取到值,所以函数f根本不会执行。
2. 对象的解构赋值
解构赋值不仅可以用于数组,还可以用于对象
let {foo,bar}={foo:'aaa',bar:'bbb'}
比较常见的是在Node导出模块对象时:
let a=1,b=2;
//let obj={a,b}
module.exports={a,b}
对象的解构与数组有一个重要的不同,数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名才能取到正确的值。
let {foo,bar}={bar:'bbb',foo:'aaa'}
let {baz}={foo:'aaa',bar:'bbb'}
上面的第一行语句,等号左边的两个变量的次序与等号右边两个同名属性的次序不一致,但是对取值完全没有影响,第二行语句中左边的变量在等号右边没有对应的同名属性名,所以取不到值,为undefined
若所需要的变量名明确与属性名不一致,则必须写成下面这样:
let {foo:baz}={foo:'aaa'};
console.log(baz);//aaa
let {first:f,last:l}={first:'hello',last:'world'}
console.log(f,l);//hello world
这实际上说明,对象的解构赋值是下面形式的简写:
let {foo:foo,bar:bar}={foo:'aaa',bar:'bbb'}
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再将该属性的值赋给模式匹配的变量,真正被赋值的是后者,而不是前者。对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
由于数组本质是一种特殊的对象,因此可以对数组进行对象属性的解构
let arr=[1,2,3];
let {0:first,[arr.length-1]:last}=arr;//方括号这种写法,属于“属性名表达式”
console.log(first,last);//1 3
3. 字符串的解构赋值
字符串也可以解构赋值,这是因为字符串此时会先被默认转化为一个类似数组的对象
const [a,b,c,d,e]='hello';
console.log(a,b,c,d,e);
let {length:len}='hello';
console.log(len);// 5 类似数组的对象都有一个length属性
4. 数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转化为对象。
let {toString:s}=123;
console.log(s);//Number.prototype.toString
let {toString:s}=true;
console.log(s);//Boolean.prototype.toString
解构赋值的规则是,如果等号右边的规则不是对象或者数组时,就先将其转化为对象。若无法转化为对象,如null
、undefined
,则会报错
5. 函数参数的解构赋值
函数的参数也可以使用解构赋值
function add([x,y]){...}
add([1,2]);
上面代码中,函数add
的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y,对于函数内部的代码来说,它们能感受到的参数就是x和y.
下面是另一个例子:
[[1,2],[3,4]].map(([x,y])=>x+y);//[3,7]
函数参数的解构赋值也可以使用默认值
function move({x=0,y=0}={}){
return [x,y]
}
move();//[0,0]
move({});//[0,0]
move({x:2});//[2,0]
move({x:2,y:3});//[2,3]
//只有当传入参数值为`undefined`时,才会考虑默认值,在阅读的时候可以先抛开默认值不看便于阅读
上面代码中,函数move
的参数是一个对象,同时设置默认值为{}
,通过对传入的对象进行解构,得到变量x
和y
的值,如果解构失败,x
、y
等于默认值。
注意,下面的写法略微有点不同
function move({x,y}={x:0,y:0}){
return [x,y]
}
move();//[0,0]
move({});//[undefined,undefined]
move({x:2});//[2,undefined]
move({x:2,y:3});//[2,3]
这里函数参数的默认值为{x:0,y:0}
,与上面不同。
6. 圆括号问题
在变量的声明语句,函数参数以及赋值语句的模式之中,都不能使用圆括号,只有一种情况下可以使用圆括号:赋值语句的非模式部分。
注意:
(1). 当解构赋值与扩展运算符一起使用时,若扩展运算符所在的位置不是最后一个参数,程序会报错。如下:
let { ...x, y, z } = obj; // 句法错误
let { x, ...y, ...z } = obj;//句法错误
反之,只使用了扩展运算符时不会出错
let obj = { x,...y,...z};
(2). 解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型,那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
网友评论