1.先来说说数组的解构赋值。
【基本用法】
ES6允许按照一定模式,从数组和对象中提取值,然后对变量进行赋值,这种被称为解构。
var a = 1;
var b = 2;
var c = 3;
//ES6:
var [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
z // []
(1)如果解构不成功,变量的值就等于undefined
var [foo] = [];
var [bar,foo] = [1];
//以上两种情况都属于解构不成功,foo的值都会等于undefined
(2)另一种情况,就是不完全解构,即等号左边的模式只匹配等号右边数组的一部分,这种情况下,解构依然可以成功。
let [x,y] = [1,2,3];
x // 1
y // 2
let [a,[b],d] = [1,[2,3],4];
a // 1
b // 2
d // 4
上面的两个例子都属于不完全解构,但是可以成功
(3)如果等号右边不是数组,不是可以遍历的解构,那么就会报错。
解构赋值不仅适用于var命令,也适用于let和const命令
var [v1,v2,...,vN] = array;
let [v1,v2,...,vN] = array;
const [v1,v2,...,vN] = array;
对于Set结构,也可以使用数组的解构赋值
let [x,y,z] = new Set(["a","b","c"])
x // "a"
事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
function* fibs(){
var a = 0;
var b = 1;
while(true){
yield a;
[a,b] = [b,a+b];
}
}
var [first,second,third,fourth,fifth,sixth] = fibs();
console.log(first); // 0
console.log(second); // 1
console.log(third); // 1
console.log(fourth); // 2
console.log(fifth); // 3
console.log(sixth); // 5
在看书上这部分的时候,不是太懂,然后通过打印每个值,发现了一丢丢原理。不过这只是我的一点理解,也不知道对不对。
上面这个fibs这个函数,里面一直在对[a,b]这个数组赋值,在进入while循环的时候,[a,b]就已经有一个值了:[0,1].然后随着while循环的继续执行,
[a,b]这个数组就一直被赋值:
[a,b] = [0,1]
[a,b] = [1,1];
[a,b] = [1,2];
[a,b] = [2,3];
[a,b] = [3,5];
[a,b] = [5,8];
然后var [first,second,third,fourth,fifith,sixth] = fibs();
解构赋值会依次从这个接口获取值。yield a,这里简单粗暴的理解为返回a的值,所以打印出来的first到sixth是0 1 1 2 3 5.
【默认值】
解构赋值允许指定默认值
var [foo = true] = [];
foo // true
[x,y = 'b'] = ['a'] //x= 'a',y = 'b'
[x,y = 'b'] = ['a',undefined] // x = 'a',y= 'b'
注意:ES6内部使用严格相等运算符(===)判断一个位置是否有值,所以,如果一个数组成员不严格等于undefined,默认值是不会生效的
var [x=1] = [undefined];
// x = 1
var [x =1] = [null];
x//null 如果一个数组成员时null,默认值就不会生效,
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候才会求值。
function f(){
console.log('aaa');
}
let [x = f()] = [1];//这时候x能取到值,所以f函数根本不会执行。
默认值可以引用解构赋值的其他变量,但该变量必须已经声明
let [x = 1,y = x] = [];//x = 1,y = 1
let [x =1,y = x] = [2];//x = 2,y = 2
let [x = 1,y = x] = [1,2];//x = 1,y = 2
let [x = y,y = 1] = [];//报错
因为x用到默认值y时,y还没有被声明
2.接着说说对象的解构赋值
解构不仅可以用于数组,还可以用于对象
var {foo,bar} = {foo:"aaa",bar:"bbb"};
foo// "aaa"
bar//"bbb"
注意:对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能找到正确的值。
var {bar,foo} = {foo:"aaa",bar:"bbb"};
foo//"aaa"
bar//"bbb"
var {baz} = {foo:"aaa",bar:"bbb"};
baz//undefined
如果变量名与属性名不一致,可以写成下面这样:
var {foo:baz} = {foo:"aaa",bar:"bbb"};
baz // "aaa"
let obj = {first:"aaa",last:"bbb"};
let{first:f,last:l} = obj
f //"aaa"
l //"bbb"
这个实际上说明,对象的解构赋值是以下形式的简写:
var {foo:foo,bar:bar} = {foo:"aaa",bar:"bbb"};
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,不是前者。
采用这种写法时,变量的声明和赋值是一体的,对于let和const而言,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。
let foo;
let {foo} = {foo:1};//报错,已经声明过了
let baz;
let {bar:baz} = {bar:1};//baz已经声明过了
注意:不过这个错误只会在使用let和const命令时出现,因为var允许重新声明
let foo;
({foo}={foo:1});//成功
和数组一样,解构也可以用于嵌套解构的对象
var obj = {p:["hello",{y:"world"}]};
var {p:[x,{y}]} = obj;
x//"hello"
y//"world"
注意这时候p是模式,不是变量,所以不会被赋值
var node = {
loc:{
start:{
line:1,column:5
}
}
}
var {loc:{start:{line:1,column:5}}} = node;
line //1
loc //loc is undefined
start // start is undefined
上面代码中,只有line是变量,loc和start都是模式,不会被赋值
下面是嵌套赋值的例子
({foo:obj.prop,bar:arr[0]} = {foo:123,bar:true});
obj//{prop:123}
arr //[true]
对象的解构赋值也可以指定默认值
var {x = 3} ={};
x //3
var {x,y = 5} = {x:1};
x // 1
y // 5
var {message :msg = "something went wrong"} = {};
msg // something went wrong
默认值生效的条件是,对象的属性值严格等于undefined
var {x = 3} = {x :undefined};
x // 3
var {x=3} = {x:null};
x // null
如果解构失败,变量的值等于undefined
var {foo} ={bar:'baz'};
foo // undefined
如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,会报错。
var {foo:{bar}} = {baz:'baz'};
foo这是等于undefined,再取子属性就会报错
//错误的写法:
var x;
{x} = {x:1};//这时候javascript引擎会把{x}理解成一个代码块,从而发生错误。
//正确的写法
({x} = {x:1});
还有解构赋值,允许等号左边的模式中不放置任何变量名:
({} = [true,false]);
({} = 'abc');
({} = [])
//这些表达式虽然没有意义,但是语法是合法的,可以执行
对象的解构赋值可以很方便的将现有对象的方法赋值到某个变量
let {log,sin,cos} = Math;
//上面将Math对象的取对数,正弦,余弦三个方法赋值到了对应的变量上,这样使用起来会方便很多
3.字符串的解构赋值
字符串也可以解构赋值。这是因为字符串被转换成了一个类似数组的对象。
const [a,b,c,d,e] = 'hello';
a // 'h' b// 'e' ...
//类似数组的对象都有length属性,因此还可以对这个属性解构赋值。
let {length:len} = 'hello'
len // 5
4.数值和布尔值的解构赋值
在解构赋值时,如果等号右边是数值或者布尔值,则先转换为对象
let {toString:s} = 123;
s === Number.prototype.toString // true
let {toString:s} = true;
s === Boolean.prototype.toString // true
上面的代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取得值
解构赋值的规则是:只要等号右边的值不是对象,就先将其转为对象,由于undefined和null无法转化为对象,所以对他们进行解构赋值都会报错。
let {prop:x} = undefined;//TypeError
let {prop:y} = null;//TypeError
5.函数参数的解构赋值
function add([x,y]){
return x+y;
}
add([1,2]) // 3 注意add的参数不是一个数组,而是通过解构得到的变量x,y
其次,函数参数的解构赋值,也可以是默认值,得看是赋值给等号左边,还是等号右边,等号左边是变量,等号右边是参数。
给定一个例子来理解
(1)默认值在等号左边
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的值。
如果解构失败,则x和y等于默认值
(2)默认值在等号右边
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]
上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种不同的结果。
这就是默认值在等号左边和等号右边的区别。一个是真正为变量赋值(左边),一个是为函数参数赋值(右边)。
还有一点:undefined可以触发函数参数的默认值
[1,undefined,3].map((x = 'yes') =>x)
6.圆括号问题
ES6的规则是,只要可能导致解构歧义,就不得使用圆括号。
因此,建议只要有可能,就不要在模式中放置圆括号
(1)不能使用圆括号的情况
1.变量声明语句中,模式不能带有圆括号
var [(a)] = [1];
var {x:(c)} = {};
var {o:({p:p})} = {o:{p:2}};
上面三条语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。
2.函数参数中,模式不能带有圆括号
函数参数也属于变量声明,因此不能带有圆括号
//报错
function f([(z)]) {return z;}
3.不能将整个模式或嵌套模式中的一层放在圆括号中、
//报错
({p:a}) = {p:42};
([a]) = [5];
(2)可以使用圆括号的情况
只有一种:赋值语句的非模式部分可以使用圆括号
[(b)] =[3]; // 正确
({p:(d)} = {});//正确
[(parseInt.prop)] = [3];//正确
上面三条都是正确的,首先他们都是赋值语句,而不是声明语句,其次,它们的圆括号都不属于模式的一部分。
7.用途
(1)变换变量的值
[x,y] = [y,x];
上面代码交换了变量x和y的值
(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将其放在数组或对象中返回。有了解构赋值,取出这些值就非常方便
//返回一个数组
function example(){
return [1,2,3];
}
var [a,b,c] =example();
//返回一个对象
function example(){
return {
foo:1,
baz:2
};
}
var {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对象中的数据尤其有用
var jsonData = {
id:42,
status:"ok",
data:[867,5309]
}
let {id,status,data:number} = jsonData;
console.log(id,status,number);
//42,ok,[867,5309]
上面的代码可以快速提取JSON数据的值
(5)函数参数的默认值
jquery.ajax = function(url,{
async = true,
beforeSend = function(){},
cache = true,
// .. more config
}){
//... do stuff
};
//指定参数的默认值,就避免了在函数体内部再写var foo = config.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);
}
//first is hello
//second is world
如果只想获得键名,或者只想获取键值,可以写成下面这样
//获取键名
for(let [key] of map){
//...
}
//获取键值
for(let [,value] of map){
//...
}
(7)输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰
const {SourceMapConsumer,SourceNode} = require("source-map");