赋值运算符之解构赋值
在ES5之前,我们在对对象进行赋值的时候,都是通过 = 等于运算符的,ES6提供了一种新的赋值方式,这种解构赋值可以通过“模式匹配”,只要两侧的模式结构相同,那么就会按照位置顺序,对变量赋值。
这种赋值的适用场景是要对多个变量进行同时赋值、交换变量的值、获取从函数返回的多个值、函数参数的定义、提取json数据等等。
数组解构赋值
适用场景:只要数据结构具有Iterator接口就可以使用数组的解构赋值。
基本使用:同时为多个变量赋值;
let [x, y, z] = [1, 2, 3];
//等价于
let x = 1;
let y = 2;
let z = 3;
如果两侧的模式结构一摸一样,那就会解构成功,但是如果两侧结构存在偏差,那么就会存在解构失败或者解构不完全的情况。
-
解构失败
let [a, b, ...rest] = ['a']; //输出结果: a undefined [] let [x, , y] = [1, 2]; /* 输出结果:1 undefined (为什么是undefined,就是因为赋值解构的原则是根据两侧的模式结构对应赋值的, 那么x对应的是1,第二个空的变量对应的是2,第三个变量y对应的是undefined) */ 从上述结果我们可以看到如果解构失败,那么对应的变量就会被赋值undefined,这里rest被赋值[],Rest 属性收集那些尚未被解构模式拾取的剩余可枚举属性键。在数组的解构赋值中,rest参数用于接收剩余的值,存储在数组中,所以如果解构失败对应返回的是空数组。
如果使用默认值,那我们就可以避免这种解构失败的情况: 解构赋值是允许设置默认值,只有当等式右边对应的值严格等于undefined,默认值才会生效。
let [q = 1, w = 2, e] = [3, , 3] //3 2 3 let [x=1, y=x] = [undefined,null]; //这里输出 1, null /* 首先明确的是默认值是可以引用赋值的,也就是说因为等式左边已经先将x赋值1了, 所以y是可以引用x的 但是如果是[x=y, y=1],这种就不可以,因为当进行解构的时候,y还没声明, 这时候如果直接将y赋值给x,那么就会报错说y还没有定义。 所以在这种情况下,如果等式右边都是undefined的话,那么x=1,y=x=1, 但是我们现在看到的是y对应右边的结构中, 它的值是null,null === undefined是false的, 所以这里是输出1,null */注意: 解构赋值的默认值是惰性赋值的,也就是说只要在等式右边的属性值是严格等于undefined的情况下,默认值才会被赋值到变量上面。也就是以下的原理
//对于 let[x=1] = [2],其底层原理是: let x; if([2][0]===undefined){ x = 1; }else{ x = [2][0] }还有一种解构失败的情况:就是当等式右边并不是iterator结构。
-
解构不完全
这种情况是左边的模式只匹配一部分右边的数组。具体实例如下
let [x, y] = [1, 2, 3]; //输出结果: 1 2
对象解构赋值
对象的解构赋值规则和数组的解构赋值规则差别不大,由于数组的特殊性,它在进行解构赋值的时候时候按照顺序来的,那么对象的解构赋值是根据属性名来匹配的。
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
//输出: bar: bbb; foo: aaa
正是因为对象的解构赋值是根据属性名来的,所以一般我们只研究其解构失败的情况
-
解构失败
对象的解构失败会将对应的变量名设置为undefined
let { bar, foo } = { foo: 'aaa' } //输出: foo: aaa; bar: undefined这里首先要区分一下【属性名】和【变量名】在对象解构中的区别: 对象解构的过程中,模式匹配是先根据寻找等式的右边是否有相同的属性名,找到相同的属性名就将对应的属性值赋值给等式左边的变量名。具体看以下实例
let {foo: baz} = {foo: 'aaa'} //ReferenceError: foo is not defined /* 这里foo是属性名,baz才是变量名 执行的流程: 1. 查找等式右边的foo属性名 2. 找到了右边的foo,将foo的值aaa赋值给左边的baz变量名 由上述可知,我们平时写的let {foo} = {foo: 'aaa'} 其实是等式let {foo: foo} = {foo: 'aaa'}的缩写 */同样的,针对解构失败,我们也可以为变量设置默认值,只有在右边等式对应的属性值严格等于undefined,默认值才会生效,作用机理同样是惰性的。
let { x: y = 5 } = { x: 1 } //这里输出的y = 1 /* 对于左侧的结构,是这样的: x是属性名,y是变量名,5是y的默认值 因为右侧的x是1,不是undefined 所以这里左侧的默认值并没有生效,所以y的值是1 */ let { x: y = 5 } = {} //y = 5对象解构的时候是可以查找原型链的,也就是说对应等式右侧的模式中,如果找不到对应的属性名,那么是会去它的原型对象中寻找的。
还需要注意的是,我们尽量不要在解构赋值的过程中使用圆括号,除了在对于已经声明好的变量进行解构赋值的时候,如下面的情况:
let x; {x} = {x:1}; //这样写是错误的,因为这样JavaScript引擎就会把{x}当成代码块来处理,而不是解构赋值 ({x} = {x:1}); //这样写才是对的,但是要注意把let x后面加一个分号,不然连在一起的话还是会把它当作一个方法,而不是解构赋值
函数解构赋值
在普通函数中,如果我们给函数定义了形参,这个过程就是在声明变量,传入实参的过程就是给刚刚声明的形参赋值,那么如果我们没有传入实参,对应的形参将会被赋值undefined;
那么当我们引入解构赋值就可以给函数的形参设置默认值。
注意以下几种函数参数解构的形式:
-
为变量x,y指定默认值,但是不是为函数的参数指定默认值
function move({ x = 1, y = 1 }) { console.log(x, y); } move({}); move({ x: 10, y: 100 }); move(); //这里会报错,因为相当于{ x = 1, y = 1 } = undefined,解构的模式不对 -
为函数的参数指定默认值,也为x,y指定默认值
function move({ x = 1, y = 1 } = {}) { console.log(x, y); } move({}); move(); //上述两个方法的调用是一样的 move({ x: 10, y: 100 });
-
为函数的参数指定默认值,不为x,y指定默认值
function move({x, y} = {x: 10, y: 100}){ console.log(x, y); } move({}); //undefined undefined move(); move({ x: 11, y: 110 });
当然还有字符串、布尔值的解构赋值等,有兴趣的可以去了解一下~