构赋值:其实就是分解出一个对象的解构,分成两个步骤:
- 变量的声明
- 变量的赋值
原理:ES6 变量的解构赋值本质上是“模式匹配”, 只要等号两边的模式相同,左边的变量就会被赋予匹配的右边的值,如果匹配不成功变量的值就等于 undefined
一、 数组的解构赋值
// 对于数组的解构赋值,其实就是获得数组的元素,而我们一般情况下获取数组元素的方法是通过下标获取,例如: let arr = [1, 2, 3]; let a = arr[0]; let b = arr[1]; let c = arr[2]; // 而数组的解构赋值给我们提供了极其方便的获取方式,如下: let [a, b, c] = [1, 2, 3]; console.log(a, b, c); //1,2,3
1.模式匹配解构赋值
let [foo, [ [bar], baz ]] = [1, [ [2], 3 ]]; console.log(foo, bar, baz); //1,2,3
2.省略解构赋值
1. let [, , a, , b] = [1, 2, 3, 4, 5]; 2. console.log(a, b); //3,5
3.含剩余参数的解构赋值
1. let [a, ...reset] = [1, 2, 3, 4, 5]; 2. console.log(a, reset); //1,[2,3,4,5]
其转成 ES5 的原理如下:
1. var a = 1, 2. reset = [2, 3, 4, 5]; 3. console.log(a, reset); //1,[2,3,4,5]
注意:如果剩余参数是对应的值为 undefined,则赋值为[],因为找不到对应值的时候,是通过 slice 截取的,如下:
1. let [a, ...reset] = [1]; 2. console.log(a, reset); //1,[]
其转成 ES5 的原理如下:
1. var _ref = [1], 2. a = _ref[0], 3. reset = _ref.slice(1); 4. console.log(a, reset); //1,[]
4.非数组解构成数组(重点,难点)
一条原则:要解构成数组的前提:如果等号右边,不是数组(严格地说,不是可遍历的解构),则直接报错,例如:
let [foo] = 1; //报错 let [foo1] = false; //报错 let [foo2] = NaN; //报错 let [foo3] = undefined; //报错 let [foo4] = null; //报错 let [foo5] = {}; //报错
为什么?转成 ES5 看下原理就一清二楚了:
var _ = 1, foo = _[0]; //报错 var _false = false, foo1 = _false[0]; //报错 var _NaN = NaN, foo2 = _NaN[0]; //报错 var _undefined = undefined, foo3 = _undefined[0]; //报错 var _ref = null; foo4 = _ref[0]; //报错 var _ref2 = {}, foo5 = _ref2[0]; //报错
5.Set 的解构赋值
先执行 new Set()去重,然后对得到的结果进行解构
1. let [a, b, c] = new Set([1, 2, 2, 3]); 2. console.log(a, b, c); //1,2,3
6.迭代器解构
function* fibs() { let a = 0; let b = 1; while (true) { yield a; [a, b] = [b, a + b]; } } let [first, second, third, fourth, fifth, sixth] = fibs(); sixth; // 5
总结 1:只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
7.解构赋值的默认值
当变量严格等于 undefined 的时候,会读取默认值,所谓的严格等于,就是“===”
-- -- -- -- -- let [a, b = 'default'] = [1]; console.log(a, b); //1,'default' -- -- -- -- -- let [c = 'default'] = [undefined]; console.log(c); //'default' -- -- -- -- -- function f() { console.log('aaa'); } let [x = f()] = [1]; console.log(x); //1 -- -- -- -- -- function f() { console.log('aaa'); //'aaa' } let [a, x = f()] = [1]; console.log(a, x); //1,undefined
总结 2:如果不使用默认值,则不会执行默认值的函数
二、对象的解构赋值
1.解构赋值的举例:
1. let p1 = { name: "zhuangzhuang", age: 25 }; let { name, age } = p1; //注意变量必须为属性名 console.log(name, age); //"zhuangzhuang",25
其转成 es5 的原理则为:
1. var _p1 = p1, 2. name = _p1.name, 3. age = _p1.age; 4. console.log(name, age); //"zhuangzhuang",25
2.解构赋值的别名
如果使用别名,则不允许再使用原有的解构出来的属性名,看以下举例则会明白:
let p1 = { name: "zhuangzhuang", age: 25 }; let { name: aliasName, age: aliasAge } = p1; //注意变量必须为属性名 console.log(aliasName, aliasAge); //"zhuangzhuang",25 console.log(name, age); //Uncaught ReferenceError: age is not defined
为何打印原有的属性名则会报错?让我们看看转成 es5 后的原理是如何实现的:
var _p1 = p1, aliasName = _p1.name, aliasAge = _p1.age; console.log(aliasName, aliasAge); //"zhuangzhuang",25 console.log(name, age); //所以打印name和age会报错——“Uncaught ReferenceError: age is not defined”,但是为何只报错age,不报错name呢?
只报错 age,不报错 name,这说明其实 name 是存在的,那么根据 js 的解析顺序,当在当前作用域 name 无法找到时,会向上找,直到找到 window 下的 name, 而我们打印 window 可以发现,其下面确实有一个 name,值为“”,而其下面并没有属性叫做 age,所以在这里 name 不报错,只报 age 的错。类似 name 的属性还有很多,比如 length 等。
3.解构赋值的默认值
有些情况下,我们解构出来的值并不存在,所以需要设定一个默认值,例如:
let obj = { name: "zhuangzhuang" }; let { name, age } = obj; console.log(name, age); //"zhuangzhuang",undefined
我们可以看到当 age 这个属性并不存在于 obj 的时候,解构出来的值为 undefined,那么为了避免这种尴尬的情况,我们常常会设置该属性的默认值,如下:
let obj = { name: "zhuangzhuang" }; let { name, age = 18 } = obj; console.log(name, age); //"zhuangzhuang",18
当我们取出来的值不存在,即为 undefined 的时候,则会取默认值(假设存在默认值),ES6 的默认值是使用“变量=默认值”的方式。
注意:只有当为 undefined 的时候才会取默认值,null 等均不会取默认值
let obj = { name: "zhuangzhuang", age: 27, gender: null, //假设未知使用null isFat: false }; let { name, age = 18, gender = "man", isFat = true, hobbies = "study" } = obj; console.log(name, age, gender, isFat, hobbies); //"zhuangzhuang",27,null,false,"study"
4.解构赋值的省略赋值
当我们并不是需要取出所有的值的时候,其实可以省略一些变量,这就是省略赋值,如下
1. let arr = [1, 2, 3]; 2. let [, , c] = arr; 3. console.log(c); //3
注意:省略赋值并不存在与对象解构,因为对象解构,明确了需要的属性
let obj = { name: "zhuangzhuang", age: 27, gender: "man" }; let { age } = obj; console.log(age); //27
5.解构赋值的嵌套赋值(易错点,重点,难点)
let obj = {}, arr = []; ({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true }); console.log(obj, arr); //{prop:123},[true]
注意当解构出来是 undefined 的时候,如果再给子对象的属性,则会报错,如下
let { foo: { bar } } = { baz: "baz" }; //报错,原因很简单,看下原理即可,如下: //原理: let obj = { baz: "baz" }; let foo = obj.foo; //foo为undefined let bar = foo.bar; //undefined的bar,可定报错
6.{}是块还是对象?
当我们写解构赋值的时候,很容易犯一个错误——{}的作用是块还是对象混淆,举例如下:
//举例一: let { a } = { a: "a" }; console.log(a); //'a',这个很简单 //很多人觉得,以下这种写法也是可以的: let a; { a } = { a: "a" }; //直接报错,因为此时a已经声明过了,在语法解析的时候,会将这一行的{}看做块结构,而“块=对象”,显然是语法错误,所以正确的做法是不将大括号写在开头,如下: let a; ({ a } = { a: "a" })
7.空解构
按照之前写的,解构赋值,左边则为解构出来的属性名,当然,在这里,我们也可以不写任何属性名称,也不会又任何的语法错误,即便这样没有任何意义,如下:
({} = [true, false]); ({} = "abc"); ({} = []);
8.解构成对象的原则
如果解构成对象,右侧不是 null 或者 undefined 即可! 之前说过,要解构成数组,右侧必须是可迭代对象,但是如果解构成对象,右侧不是 null 活着 undefined 即可!
三、字符串的解构赋值
.字符串也是可以解构赋值的
1. const [a, b, c, d, e] = "hello"; 2. console.log(a, b, c, d, e); //'h','e','l','l','o'
2.转成 es5 的原理如下:
1. var _hello = "hello", 2. a = _hello[0], 3. b = _hello[1], 4. c = _hello[2]; 5. 6. console.log(a, b, c);
3.注意:字符串有一个属性 length,也可以被解构出来,但是要注意,解构属性一定是对象解构
1. let { 2. length 3. } = "hello"; 4. console.log(length); //5
布尔值和数值的解构,其实就是对其包装对象的解构,取的是包装对象的属性
{ toString: s } = 123; console.log(s); //s === Number.prototype.toString { toString: s } = true; console.log(s); //s === Boolean.prototype.toString
总结:解构赋值的规则是:
- 解构成对象,只要等号右边的值不是对象或数组,就先将其转为对象。由于 undefined 和 null 无法转为对象,所以对它们进行解构赋值,都会报错。
- 解构成数组,等号右边必须为可迭代对象