变量的解构赋值
解释:
1. 简单的来说,可以粗暴地理解为,等号左右两边的数组/结构类似时,将右边的值直接赋给左边对应位置的变量;
2. 主要面对的是数组和对象;
3. 虽说数值、字符串以及函数参数也能用,但核心思想是基于数组和对象的,理解好第二点,那么理解后面的也不难;
数组的解构赋值
【1】标准情况:
等号左右数组结构相同,左边的变量直接拿右边对应位置的变量的值。
如以下代码;
//基本版
var [a,b,c]= [1, 2, 3];
a; //1
b; //2
c; //3
左边是一个长度为3的数组,右边也是一个长度为3的数组。
区别是左边是变量,右边是已知的值。
这样左右一一对应,变量a、b、c就分别被赋值了;
【2】左右结构相同,但有些位置没有对应的值;
如以下代码,原本b的位置没有变量了,这样的代码是可以跑通的。
//左缺失
var [a,,c]= [1, 2, 3];
a; //1
c; //3
又如以下代码,左边齐全,但是右边某个位置缺少变量。
这种情况下,无法对应的变量将被赋值undefined
//右缺失,解构不成功但不报错
var [a,b,c]= [1, , 3];
a; //1
b; //undefined
c; //3
【3】数组中嵌套数组
普通嵌套,一一对应的,此时对应位置对应值,理解起来简单暴力;
//数组嵌套
var [a, [b, c], d]= [1, [2, 3], 4];
a; //1
b; //2
c; //3
d; //4
假如在以上基础上,左右位置有缺少,则如同【2】中缺少的情况一样,变量的值则为undefined,或者右边某个值没有找到左边对应的某个变量。
代码略
左边和右边非完全对应,如以下代码,则右边非对应的部分可以视为一个整体,或者左边解构失败报错
//左边对应右边某个元素
var [a, b, c]= [1, [2, 3], 4];
a; //1
b; //[2,3],视为整体
c; //4
//无法解构,抛出异常
var [a, [b, c], d]= [1, 2, 3]; //Uncaught TypeError: undefined is not a function
//扩展运算符... (三个句号,更多了解请参照以后的内容)
var [a, ...b]= [1, 2, 3, 4];
b; //[2, 3, 4]
【4】会报错的情况
具体来说,右边不是数组(严格的说,不是可遍历的结构),那么将会报错。
// 报错的情况
let [foo] = 1; //number
let [foo] = false; //boolean
let [foo] = NaN; //NaN
let [foo] = undefined; //undefined
let [foo] = null; //null
let [foo] = {}; //object
这时,我们回过头看【3】中抛出异常的那段代码,之所以报错,是因为实质上是[b, c] = 2,无法结构,所以报错。
【5】var、let、const都适用
【6】对于Set结构,也可以使用其来结构(在Set写完后补充);
【7】继续拓展,凡是具有Iterator接口的数据结构,都可以使用数组的形式进行结构赋值。
别问我什么是Iterator接口,请看后面专门讲Iterator的内容。
带默认值的数组解构赋值
解释:
1. 简单来说,就是在解构赋值时,给一个默认值;
2. 然后先看能不能解构成功,如果成功则取解构到的值,如果失败则取默认值;
【1】标准情况下:
var [a, b = 2, c, d = 10]=[1, , 3, 4];
a; //1
b; //2
c; //3
d; //4
在以上代码中,a和c和普通的解构赋值相同。
除此之外,左边多了一个b = 2和d = 10。于是,b的默认值是2,d的默认值是10,
而这两者的区别是右边对应解构位置,b的位置对应的是空内容,而d的位置对应的是4
因此,有默认值但解构失败的(准确的说是undefined)b,取了自己的默认值2;
有默认值但解构成功的的,则取了自己解构成功后的值4;
【2】右边不写和undefined是一样的
var [a = 1] = [undefined]; //1
var [a = 1] = [null]; //null
var [a = 1] = [3]; //3
var [a = 1] = ["abc"]; //"abc"
var [a = 1] = [{m: 1}]; //{m:1}
var [a = 1] = [true]; //true
var [a = 1] = [NaN]; //NaN
【3】在使用之前已经被声明的变量可以作为默认值使用。
简单来说,假如变量a先被声明,然后作为b解构时的默认值,那么是可以的;
var a = 1;
var [b = a] = [];
b; //1
如果你不使用let、const等ES6新规定的声明模式,那么即使你先使用后声明也可以(变量提升),虽然没有什么用,并且不推荐
var [b = a] = [];
var a = 1;
b; //undefined
另外,严格模式下,以上这种声明方式不会报错。
对象的解构赋值
解释:
1. 简单来说,对象解构是根据key给变量赋值该key的val,数组解构是根据顺序给对应位置的变量赋值;
【1】标准情况
最简单的如以下代码:
var {'abc': a} = {'abc': 1};
a; //1
两边都是对象,都有一个key是abc,区别是左边是变量名a,右边的是值1
于是同key的被赋值了。
【2】变量名就是key
如以下代码,右边有key是abc,左边直接放一个和key相同的变量名,则该变量被赋值同名key的值
var {abc} = {'abc': 1};
abc; //1
以上代码的实质是:
var {abc: abc} = {'abc': 1};
声明时,第一个abc是key,第二个abc是变量名
【3】解构失败,简单来说,就是该变量没有对应的key,取值undefined
var {abc} = {};
abc; //undefined
【4】脑洞一下,【1】和【2】情况结合下是什么?
答案是都起作用
var {a: b, a} = {a: 1};
a; //1
b; //1
第一个a是key,于是变量b被取值;
第二个a是变量名,所以该变量a被取值1;
【5】对已有变量进行解构赋值
具体来说,之前我们都是在声明变量时进行结构赋值(注意有var),那么假如已经声明了一个变量,然后进行解构赋值呢?
答案是可以,但需要用圆括号将解构赋值那段代码括起来。
假如该行没有被括起来,那么就会报错;
原因在于,没有var且没有被括号括起来的话,{a}被认为是代码块,而一个对象显然是不能被赋值给一个代码块的
var a;
({a} = {a: 1}); //括号括起来,正常运行
var a;
{a} = {a: 1}; //Uncaught SyntaxError: Unexpected token =
以下代码是可以正常运行的,相当于声明了2次变量a
var a;
var {a} = {a: 1};
使用let和const时,不能声明两次,否则会报错(见const和let相关内容),但var可以
let a;
let {a} = {a: 1}; //报错,因为let不能2次
var a;
var {a} = {a: 1}; //不会报错, 2次var是允许的
一句话总结:
已声明变量,在解构赋值时需要用括号括住整行代码。
【6】解构赋值时,赋值形式并非深度复制
具体来说,如果被赋值的变量,对应的是一个对象(或数组),那么解构赋值时,赋值形式是以按引用传递时赋值的。
如以下代码,变量a的属性b是一个对象,他被赋值给变量b;
然后修改变量b中的属性c时,变量a中的属性b的属性c的值也随之变化了;
说明这是按引用传递,就像在正常情况下,将一个对象赋值给另外一个变量一样。
var a = {
b: {c: 1}
};
a.b.c; //1
var {b} = a;
b.c; //1
b.c = 2;
b.c; //2
a.b.c; //2
一句话总结:
解构赋值时,赋值形式【不是】深度复制
【7】用解构赋值可以方便的取出其他对象中的某个方法或属性
如以下代码中,将对象computed的方法取出来使用,简单明了
var computed = {
add: function (a, b) {
return a + b;
},
minus: function (a, b) {
return a - b;
}
};
var {add, minus} = computed;
add(2, 3);
minus(2, 3);
或者某个对象中有一个属性是对象,我们只需要修改这一个对象而不用修改其他属性时,就可以采用这样的方法将属性赋值给一个变量,然后修改这个变量即可(利用对象被赋值时的按引用传递的性质)
【8】数组可以视为带key的对象来进行解构赋值
简单来说,假如有一个数组arr,那么arr[0]就是数组的第一个元素,arr[1]就是数组的第二个元素,0和1就是这个数组的key
如以下代码
var arr = [2, 4, 6, 8];
var {
0:a,
1:b,
[arr.length - 1]:d,
[arr.length - 2]:c
}= arr;
a; //2
b; //4
c; //6
d; //8
但相反情况下,是不可以的。
var obj = {
0: 1,
1: 2
}
var [a,b] = obj; //Uncaught TypeError: undefined is not a function
一句话总结:
左边数组右边只能是数组;左边对象,右边可以是数组,或对象
【9】对象的默认值
简单来说,就是对象在解构赋值时也可以赋默认值,如果解构成功不是undefined,则取解构的值,否则取默认值
如代码:
var {
a: A = 10,
b:B,
C=20
}={b: 1};
A; //10
B; //1
C; //20
字符串的解构赋值
解释:
1. 字符串可以被解构赋值,视为一个类似数组的对象
【1】右边可以当做一个数组看待
如以下代码的str,就类似[“H”, “e”, “l”, “l”, “o”, ” “, “w”, “o”, “r”, “l”, “d”, “!”]这样一个数组
var str = "Hello world!";
var [first,second] = str;
first; //H
second; //e
【2】但他终究还是一个字符串,可以用解构赋值的形式,取出他作为字符串的各种方法
比如在以下代码中,就取出了split这个方法,并且用apply来让另外一个字符串作为this而调用
var str = "Hello world!";
var {split:method} = str;
method.apply("abc", [""]); //["a", "b", "c"]
但是仔细想想,这个似乎也没有什么意义。
字符串继承的是String的方法,如果需要字符串的方法,干嘛不直接从String中去获取呢?
其他基本类型的解构赋值
解释:
1. 尝试转为对象,如果能转则可以解构赋值,比如布尔类型和数值类型;
2. 不能转则不能解构赋值,比如null和undefined;
【1】数值和布尔的值不能被取出
因为不能被转为数组或对象,因此他的值不能被取出;
但是可以取出他的方法,准确的说,是Number和Boolean的方法,参照上面的字符串的【2】内容;
【2】null和undefined无法解构
准确的说,是会报错,因为他不能被转为对象。
var un = undefined;
var {abc} = un; //Uncaught TypeError: Cannot match against 'undefined' or 'null'.
函数参数的解构赋值
解释:
1. 函数参数可以解构赋值,解构方法和上面相同。
2. 函数参数可以有默认值,有点类似c++函数参数声明时的默认值。
【1】函数参数的解构赋值
先看代码
function add({x, y}) {
return x + y;
}
add({x: 1, y: 2}); //3
Babel是这么处理以上代码以适配es5的,即将参数变为一个变量,然后分别将该变量的x属性和y属性赋值给变量x和遍历y,然后拿去用。
function add(_ref) {
var x = _ref.x,
y = _ref.y;
return x + y;
}
add({ x: 1, y: 2 });
类似的还有数组,道理是相同的,注意需要按格式写,比如function([a, b])和function(a, b)是完全不同的,前者可以被解构赋值,而后者不行
【2】函数参数的默认值
函数参数也可以有默认值,比如以下代码
function add({x=10, y=20}) {
return x + y;
}
add({x: 1, y: 2}); //3
add({x: 1}); //21
add({}); //30
函数也可以这么声明,效果是一样的。
function add({x=10, y=20}={}) {
return x + y;
}
另外,右边的{}可以写东西,至于写了有什么用,要参考下面的代码,应该是完整版了
左、右都有默认值的情况下,各种情况
function show({x=10, y, z=100}={y: 5, z: 50}) {
return [x, y, z];
}
show({x: 1, y: 2, z: 3}); //[1, 2, 3] 取传参
show({x: 1, y: 2}); //[1, 2, 100] 先传参再左,无右
show({x: 1}); //[1, undefined, 100] 先传参再左,无右
show({y: 1}); //[10, 1, 100] 先传参再左,无右
show({z: 1}); //[10, undefined, 1] 先传参再左,无右
show({}); //[10, undefined, 100] 先传参再左,无右
show(); //[10, 5, 50] 无传参时,先右,剩下的取左
简单总结一下:
(1)有传参时,永远优先使用传的参数的值;
(2)有传参时,传的参数未传的属性,如果等号左边有默认值,则取等号左边的值;
(3)有传参时,忽略等号右边的默认值;
(4)无传参时,先取等号右边的默认值,剩下的取等号左边的默认值。
一句话总结:
传参大于非传参,无传参时,右边的当做传参
关于解构赋值时的圆括号
【1】简单的看了一下,能不使用圆括号就避免使用圆括号,因为是容易出错。
具体来说,禁止使用的情况:
(1)变量声明语句禁止;
(2)函数参数中,使用解构时禁止;
(3)先声明后解构赋值时,左边、或者右边,全部被圆括号括起来,禁止;(只有上面说允许的那个情况才可以,具体看上面)
允许的情况:
(1)赋值语句的非模式部分。
以上具体总结来说,就是有的代码,赋值时是正常可以跑的,但是前面加个var之类变成声明语句,就会报错。
具体参照阮一峰的博客吧,我就不细写了,个人建议还是如非必要,尽量避免使用圆括号;
禁止和允许使用圆括号的链接
变量解构赋值的用途
【1】交换变量的值
不适合交换对象的属性,会出错
//交换对象会出错,因为按引用传递
var obj = {
x: 1,
y: 3
};
console.log(obj); //{x:1, y:3}
({
x: obj.y,
y: obj.x
} = obj);
console.log(obj); //{x:1, y:1}
var x = 1;
var y = 2;
[x, y] = [y, x];
[x, y]; //2,1
【2】在函数返回多个值时方便取出
函数默认只能返回一个值,
虽然我们也可以通过返回数组或者对象,来变相达到返回多个值,但将对象取出使用时就比较麻烦。
通过解构赋值,我们可以轻松将返回值分别赋给多个变量
function test() {
return [1, 2, 3];
}
var [a,b,c] = test();
[a,b,c]; //[1,2,3]
【3】函数参数的定义
比如说,我们需要函数的参数有a,b,c三个。但是他们顺序是不确定的,我们传参的时候就需要注意顺序,
即function test(a, b, c){},假如参数a,b,c的值分别是2,4,6,那么就应该这样调用test(2,4,6)
有了解构赋值,我们就可以这么写,无视顺序了:
function test({a, b, c}) {
console.log([a, b, c]);
}
test({b: 4, c: 6, a: 2}); //[2,4,6]
【4】提取json的值
比如ajax拿到一个用户信息,他是一个对象,比如是变量result,我们正常来说需要通过比如result.id, result.name等来获取值。
但现在可以通过结构变量,直接赋给变量id和name就行。
【5】函数参数给默认值
假如我们函数参数需要默认值,以前一般是写在函数里,要判断有没有传参,现在可以直接写在函数声明时了。
【6】获取模块的指定方法
比如我们调用了一个模块,需要使用它的a,b方法,之前是先将这个模块赋值给变量m,然后通过m.a和m.b来调用,现在可以直接赋值给a和b变量(也可以赋值给其他名字的变量);
【7】遍历Map结构
可以拿key和val去做些你想做的事情了,写法更简单。