1.数组的解构赋值
2.对象的解构赋值
3.字符串的解构赋值
4.数值与布尔值的解构赋值
5.函数参数的结构赋值
6.解构赋值中的圆括号问题
7.解构赋值的用途
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
在ES5之前,为变量赋值,只能直接指定值。
let a = 1;
let b = 2;
let c = 3;
console.log(a);
console.log(b);
console.log(c);
在ES6中是可以这样赋值的。
let [a,b,c] = [1,2,3];
console.log(a);
console.log(b);
console.log(c);
上面代码,可以从数组中取值,按照对象位置,对变量赋值。本质上,这种写法属于‘模式匹配’,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
还有以下形式都可以进行数组的解构赋值。
let [foo,[[bar],num]] = [1,[[2],3]];
console.log(foo); //1
console.log(bar); //2
console.log(num); //3
let [,,third] = ['foo','bar','baz'];
console.log(third); // 'baz'
let[x,,y] = [1,2,3];
console.log(x); //1
console.log(y); //2
let [head,...tail] = [1,2,3,4];
console.log(head); //1
console.log(tail); //[2,3,4]
let [x,y,...z] = ['a'];
console.log(x); //a
console.log(y); //undefined
console.log(z); //[]
在解构过程中,如果结构不成功,变量的值就变成undefined。
let [foo] = [];
console.log(foo); //undefined
let [bar,foo] = [1];
console.log(foo); //undefined
不完全解构
等号左边的模式,只能匹配一部分的等号右边的数组,这种情况下,解构依然可以成功。
let [x,y] = [1,2,3];
console.log(x); //1
console.log(y); //3
let [a,[b],c] = [1,[2,3],4];
console.log(a); //1
console.log(b); //2
console.log(c); //4
如果等号右边是不可遍历的结构,那么将会报错。
let [foo] = 1; //保错
let [foo] = false; //保错
let [foo] = 'string';
console.log(foo); // s
let [foo] = NaN; //报错
let [foo] = undefined; //报错
let [foo] = null; //报错
let [foo] = { '1' : '123'}; //报错 intermediate value is not iterable
console.log(foo);
上面6个出错的原因是,等号右边的值,要么转换成对象以后不具备Iterator借口(前五个) 要么本身就不具有Iterator借口(最后一个)。
后面学到的Set借口也可以用数组进行解构赋值,虽然现在我也不懂Set是什么东西。
let [x,y,z] = new Set(['a','b','c']);
console.log(x);
console.log(y);
console.log(z);
事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
function* fibs(){
let a = 0;
let b = 1;
while(true){
yield a;
[a,b] = [b,a + b];
}
}
let [first,second,thirt,fourth,fifth,sixth] = fibs();
console.log(first); //0
console.log(second); //1
console.log(thirt); //1
console.log(fourth); //2
console.log(fifth); //3
console.log(sixth); //5
上述代码中, fibs是一个Generator函数,现在不知道它是干嘛的,也不用知道,只知道它有一个Interator接口就行了。解构赋值会一次从这个接口获取值。
解构赋值的默认值
解构赋值是允许使用默认值的。在ES6内部是严格使用===运算符的,判断一个位置是否有值。当一个数组成员严格等于undefined时候,默认值生效。否则不生效。
let [foo = true] = [];
console.log(foo); //true
let [x , y = 'b'] = ['a'];
console.log(x + " " + y); // a b
let [x,y = 'b'] = ['a',undefined];
console.log(x + ' ' + y); a b
let [x,y = 'b'] = ['a','undefined'];
console.log(x + ' ' + y); // x undefined
let [x = 1] = [undefined];
console.log(x); //1
let [x = 1] = [null];
console.log(x); //null
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f(){
console.log('123');
}
let [x = f()] = [1]; //x = 1
let [x = f()] = [undefined]; //f函数执行 输出123
let [x = f()] = []; //f函数执行,输出123
默认值可以引用解构赋值中的其他变量,但是该变量必须赋值,否则报错。
let [x = 1,y = x] = [];
console.log(x,y); // x = 1, y = 1
let [x = 1,y = x] = [2];
console.log(x,y); // x = 2, y = 2
let [x = 1,y = x] = [1,2];
console.log(x,y); //x = 1, y = 2;
let [x = y, y = 1] = [];
console.log(x,y); //ReferenceError x is not defined
解构不仅可以用于数组,还可以用于对象。对象的解构与数组有一个很重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性是没有次序的,变量必须与属性名相同,才能取到正确的值。
let {name} = {name : 'wang'};
console.log(name); //wang
当等号左边的两个变量的次序与等号右边两个同名属性的次序不一致,但对取值完全没有影响;当等号右边没有等号左边的同名属性的时候,会导致取不到值,最后值为undefined。
let {foo,num} = {num : 1 , foo : 'abc'};
console.log(foo); //abc
console.log(num); //1
let {num} = {foo : 'abc'}
console.log(num); //undefined
当变量名和属性名不一致的时候又想让变量取到值,也是有办法。
let {foo : num} = {foo : 1, bar : 'abc'};
console.log(num); //1
对象的解构赋值的内部机制,是先找到同名属性,然后再赋予对应的变量。真正被赋值的是后者,而不是前者。同样的,在对象解构赋值中,等号左边对象中的属性是匹配模式,属性的值才是变量,真正被赋值的是变量(属性的值),而不是模式(对象属性)。
let { foo : foo,bar : bar} = { foo : 'abc', bar : 'bcd'};
console.log(foo); //abc
console.log(bar); //bcd
对象的解构中也是可以存在嵌套。数组解构和对象解构嵌套。
let obj = {
p : [
'Hello',
{ y : 'Word'}
]
}
let{p,p : [x,{y : baz}]} = obj;
console.log(p,x,baz); //['Hello' ,{ y : 'Word'}] 'Hello' 'Word'
对象的解构也是可以指定默认值的,同样默认值生效的情况,为空或者为undefined。
let { x = 3} = {};
console.log(x); //3
let { x = 4} = {x : undefined};
console.log(x); //4
let { obj : bar = "默认值"} = { obj : bar};
console.log(bar) //默认值
如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会出现错误,值为undefined。
let {foo : {bar}} = {foo : "123"};
console.log(bar) //undefined
如果将要一个已经声明的变量用于结构赋值,那么必须慎用。
let x;
{x} = {x : 1}; //报错
//上面代码报错的原因是js引擎将{x}理解为代码块,从而发生语法错误,只有不将大括号写在行首,避免js引擎将{}理解为代码块,才能生效。当然也是有办法运行的
let num;
({num} = {num : 2});
console.log(num) //2
//这样就可以了
解构赋值允许等号左边的模式中,不放置任何变量名,因此,可以写出非常古怪的赋值表达式,虽然语法是合法的,但是毫无意义。
console.log( ( {} = [false,true]) );
console.log( ( {} = "abc") );
console.log( ( {} = []) );
对象的解构赋值可以很方便的将现有对象的方法和属性复制到某个变量上去
let {
innerHeight : h,
innerWidth : w,
} = window;
//上述代码将window上的可视化界面的宽高赋值到了对象上。
由于数组本身也是特殊的对象,因此也可以对数组进行对象属性的结构。
let arr = [1,2,3,4];
let {
0 : first,
1 : second,
length : len,
[arr.length - 1] : last,
this : arrThis,
}
由于字符串是可以调用包装类的,因此也可以像对象一样访问属性的
let [a,b,c,d] = "abcd";
let {length : len} = "abcd";
在解构过程中,如果等号的右边的值是数值或者布尔类型,则会先转换为对象。
let {toString = s} = 123;
console.log(s) //function
console.log(s === Number.prototype.toString) //true
let {toString = s} = true;
console.log(s) //function
console.log(s === Boolean.prototype.toString) //true
在解构赋值过程中,只要等号的右边的值不是对象或者数组或者数组,那么就会先将其转化为对象,由于undefined和null无法转换为对象,所以对他们的解构就会报错。
let { toString : x} = undefined;
let { toString : x2} = null;
console.log(x,x2);
函数在传参的时候,实参和形参部分,表面上可以看做是一个数组的形式,但当参数在传的那一刻,数组参数就被解构成了变量。对于函数内部来说就是形参的变量。
function add([x,y]){
return x + y;
}
add([1,2]) //3
console.log( [[1,2],[3,4]].map( ([a,b]) => a + b)); //[3,7]
函数的参数的解构赋值也是可以使用默认值的
function move({x = 0, y = 0}){
return [x,y];
}
console.log(move({x:3,y:5})) //[3,5]
console.log(move({x:3})) //[3,0]
console.log(move({})) //[0,0]
console.log(move()) //报错
//为什么没有任何参数就报错了呢,这种现在在开发过程也是会出现的,是因为上面函数参数的默认值只是对象的结构赋值,函数的参数是个对象,并没有为函数的参数设置默认值。
function move({x = 0 , y = 0 } = {}){
return [x,y];
}
console.log(move()); //[0,0]这样就不会报错了
同样的undefined也会触发函数默认值
function([1,undefined,3].map(x = "a") = x) //1,a,3
解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式还是表达式,必须解析到(或解析不到)等式才能知道。由此带来的问题是,如果模式中出现圆括号怎么处理。ES6的规则是,只要有可能导致解构的歧义,就不得使用圆括号。但是,这条规则实际上不那么容易辨别,处理起来想当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。
不得使用圆括号的情况:
(1)变量声明语句
let [(a)] = [1]; //报错
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}] = [{}, {}]; //报错
可以使用圆括号的情况:
//赋值语句的非模式部分,可以使用圆括号
[(b)] = [3];
console.log(b); //3
({p : (d)} = {p : 5});
console.log(d); //5
[(parseInt.prop)] = [3];
console.log(parseInt.prop);
1.可以交换变量的值
let a = 5;
let b = 3;
[a,b] = [b,a]
console.log(a,b); //3 5
2.需要从数组中返回多个值,由于函数只能返回一个值,所以可以用解构赋值的方法来返回。
function example(){
return {
name : "wang",
age : 18,
}
}
let {name : n,age :g} = example();
console.log(n,g) //wang 18
3.函数参数的定义:解构赋值可以方便的将一组参数与变量名对应起来。
function fun([x,y,z]){
console.log(x,y,z);
}
fun([1,2,3]);
4.提取JSON数据:解构赋值对提取JSON对象中的数据,尤其有用。
let jsonData = {
id : 1,
status : "OK",
data : 1,
}
let {id,status,data} = jsonData;
5.函数参数的默认值,指定参数的默认值,就避免了再函数体内部写var foo = config.foo || "default foo";
$.ajax = function({
async = true,
beforeSend = function(){},
global = true
}){}
6.遍历Map结构:任何部署了Iterator接口,都可以用for ... of循环遍历,Map结构原生支持Iterator接口,配合变量的结构赋值,获取键名和键值就非常方便。
const map = new Map();
map.set("first","hello");
map.set("second","world");
for(let [key,value] of map){
console.log(`${key} is ${value}`);
}
7.输入模块的指定方法
const {
SourceMapConsumer,
SourceNode,
} = require("source-map");
//加载模块,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰
主页传送门