一、什么是解构赋值?它的基本用法。
ES6允许按照一定模式从数组和对象中提取值,然后对变量进行赋值,这被称为解构。
//下面四句分别是对a,b,c分别赋值
let arr = [1,2,3];
let a = arr[0];
let b = arr[1];
let c = arr[2];
//下面一句话搞定上面的四句话,这就是解构赋值
let [a,b,c] = [1,2,3];
//这是JSON解构赋值
let {a,b,c} = {a:1,b:2,c:3};
//复杂结构的解构赋值
let [{a,b},[n1,n2,n3],num,str] = [{a:1,b:2},[6,7,8],10,'hello']
console.log(a,b,n1,n2,n3,num,str); //1,2,6,7,8,10,hello
二、解构赋值的要求
- 左右两边结构必须一样
- 右边必须是个东西
- 声明和复制不能分开(必须在一起完成)
三、解构不成功和不完全解构
1、结构不成功:结构不成功,变量的值就会等于undefined
let [x,y,...z] = ['a']; //这是解构不成功
//x = a;
//y = undefined
//z = []
2、不完全解构,但是解构还是可以成功的。
//这是不完全解构,但是也可以解构成功
let [x,y] = [1,2,3];
//x = 1;
//y = 2;
3、如下方式都是会报错的结构。事实上,某种数据结构具有IIterator接口,就可以才有数组形式的解构赋值。
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
//报错原因:等号右边的值或是转为对象以后不具备Iterator接口(前五个),或是本身就不具备Iterator接口(最后一个表达式)
四、解构赋值允许指定默认值
1、解构赋值如何指定默认值
let [x,y='b'] = ['a'];
//x='a',y='b'
2、ES6内部使用严格相等运算符(===)判断一个位置是否有值,所以如果一个数组成员不严格等于undefined,默认值就不会生效。
let [x = 1] = [undefined] //x=1
let [x = 1] = [null] //x=null,原因是null不严格等于undefined
3、默认值可以引用解构赋值的其他变量,但是该变量是必须声明过的。
let [x = y,y = 1] = []; //ReferenceError,原因是x用到y的时候,y还没有声明
五、对象的解构赋值
1、对象的解构与数组的解构有一个重要的不同。数组的元素是按次序排列的,变量的取值是由它的位置决定的,而对象的属性没有次序,变量必须与属性同名才能取到正确的值。如果左边的变量名在右边找不到相对应的属性名,则就会等于undefined。
let {bar,foo} = {foo:'aaa',bar:'bbb'}
//bar = bbb
//foo = aaa
2、如果变量名与属性名不一致,则可以按照如下方式书写
var {foo:baz} = {foo:'aaa',bar:'bbb'};
//baz="aaa"
3、实际上对象的解构赋值是按下面形式的简写
let {foo:foo,bar:bar} = {foo:'aaa',bar:'bbb');
4、对象赋值的内部机制是先找到同名属性,然后再赋值给的对应的变量。真正被赋值的是前者,而不是后者。
let {foo:baz) = {foo:'aaa',bar:'bbb'};
//baz = "aaa"
//foo:foo is not defined
//foo是匹配模式,baz才是变量,真正被赋值的是baz,而不是模式foo
5、解构也可以用于嵌套解构的对象
let obj = {
p:[
'hello',
{y:'World'}
]
};
let {p:[ { x , { y } ] } = obj;
//x="Hello"
//y="World"
//p是匹配模式,笔试变量,因此不会被赋值
//如果p要作为变量被赋值,可以这样写
let obj = {
p:[
'hello',
{y:'World'}
]
};
let {p,p:[ { x , { y } ] } = obj;
//x="Hello"
//y="World"
//p=["hello",{y:"world"}];
6、对象的解构也可以指定默认的值。默认值生效的条件是,对象的属性值严格等于undefined。
var {x = 3} = {x:undefined};
//x=3
var {x = 3} = {x:null};
//x=null
7、 如果解构失败,变量的值就会等于undefined。
let {foo} = {bar:"baz"};
//foo = undefined
8、如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。
let { foo: { bar } } = {baz:'baz'};
//foo属性对应一个子对象。该子对象的bar属性在解构时会报错。原因是,因为foo此时等于undefined,再取子属性就会报错
10、如果将一个已经声明的变量解构赋值,必须非常小心。
let x;
{x} = {x:1};
//SyntaxError:syntax error。原因是JavaScript会把{x}理解为一个代码块,从而发生语法错误。只要不将大括号写在首行,就可以避免这个问题。
let x;
({x} = {x:1});
11、圆括号与解构赋值的关系。解构赋值允许等号左边的模式之中不放置任何变量名。因此可以写出如下表达式。
( {} = [true,false]);
( {} = 'abc' );
( {} = [] );
12、对象的解构赋值可以很方便地将现有对象的方法赋值到某个变量。
let { log, sin, cos } = Math;
将Math对象的对数,正弦,余弦三个方法赋值到对应的变量上,使用起来就会很方便。
13、由于数组也是对象,因此可以对数组进行对象属性的赋值。
let arr = [1,2,3];
let { 0:first,[arr.length-1]:last } = arr;
//first = 1
//last = 3
六、字符串的解构赋值
1、字符串也可以解构赋值,这是因为每一个字符被转换成了一个类似数组的对象。
const [a,b,c,d,e] = 'hello';
//a='h'
//b='e'
//c='l'
//d='l'
//e='o'
2、类似数组的对象有一个length属性,因此可以对这个属性进行解构赋值。
let { length:len } = 'hello';
// len=5
七、数值和布尔值的解构赋值
1、解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。解构赋值的规则是,只要等号右边不是对象或者数组,就像将其转化为对象。undefined和null无法转换为对象,会报错。
let {toString:s} = 123;
s === Number.prototype.toString //true
let { toString:s } = true;
s === Boolean.prototype.toString //true
八、函数参数的解构赋值
1、函数参数也可以使用解构赋值
[ [1,2] , [3,4] ].map(([a,b]) => a+b);
// [3,7]
2、函数参数的解构也可以设置默认值。
function move({ x=0, y=0} = {}){
return [x,y];
}
move({x:3,y:8}); //[3,8]
move({x:3}); //[3,0]
move({}); //[0,0]
move(); //[0,0]
//如下写法就会和上面的结果不一样,代码是为函数move参数设置了默认值,而不是为变量x和变量y指定默认值,所以与前一种写法不同的结果。
function move({ x, y} = {x:0,y:0}){
return [x,y];
}
move({x:3,y:8}); //[3,8]
move({x:3}); //[3,undefined]
move({}); //[undefined,undefined]
move(); //[0,0]
//undefined会触发函数的默认值
[1, undefined, 3].map((x = 'yes' => x);
//[1, 'yes', 3]
九、圆括号问题
解构赋值,对于编译器来说,到底是模式还是表达式,没有办法一开始就知道,要解析到等号才知道。因此,带来了一个心得问题,如果模式种出现圆括号该怎么处理?ES6的规则是,只要有可能导致解构的歧义,就不得使用圆括号。因此建议,只要有可能,就不要在模式中放置圆括号。
1、不能使用圆括号的三种情况
(1)变量声明语句
//变量声明语句,模式不能使用圆括号
let [(a)] = [1];
let {x:(c)} = {};
let ( {x:c} ) = {};
let { (x:c) } = {};
let {(x):c} = {};
let { o:({p: p}) } = {o:{p:2}};
(2)函数参数
//都会报错
function f([z]) { return z; };
function f([z, (x)]) { return x; };
(3)赋值语句的模式
//会报错
({ p:a }) = { P: 42 }
([a]) = [5]
[({ p:a }), {x:c}] = [{},{}];
2、可以使用圆括号的情况
(1)可以使用圆括号的情况只有一种,赋值语句的非模式部分可以使用圆括号。
[(b)] = [3]; //模式是取数组的的第一个成员
({ p:(d) } = {}]; //模式是p不是d
[(parseInt.prop)] = [3]; //
//上面的语句都是赋值语句,而不是声明语句,另外他们的圆括号不属于模式的一部分。
十、解构赋值的用途
1、可以交换变量的值
let x = 1;
let x = 2;
[x,y] = [y,x];
2、从函数返回多个值
//函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。
//返回数组
function example(){
return [1,2,3];
}
//返回对象
function example(){
return {
foo:1,
bar:2
}
}
3、函数参数的定义(解构赋值可以方便地将一i组参数与变量名对应起来)
//有次数
function f([x,y,z]) {...}
f([1,2,3]);
//无次序
function f([x,y,z]) {...}
f([x:3,y:2,z:1]);
4、提取JSON数据
let jsonData= {
id:41,
status:"ok",
data:[867,5309]
};
let {id,status,data} = jsonData;
//id=42
//status = "ok"
//data=[867,5309]
5、函数参数的默认值
jQuery.ajax = function (url,{
async = true,
beforeSend = function(){},
cache = true,
complate = function(){},
crossDomain = false,
gloabal = true,
//more config
}){
//do stuff
};
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);
}
//first is hello
//second is world
//只获取键值
for(let [,value] of map){
//...
}
//只获取键名
for(let [value] of map){
//...
}
十二、输入模块的指定方法
加载模块时,往往需要指定输入的方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
参考:阮一峰的ES6入门的第三版