解构赋值:其实就是分解出一个对象的解构,分成两个步骤:
原理: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
let [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo, bar, baz); //1,2,3
let [, , a, , b] = [1, 2, 3, 4, 5];
console.log(a, b); //3,5
let [a, ...reset] = [1, 2, 3, 4, 5];
console.log(a, reset); //1,[2,3,4,5]
其转成 ES5 的原理如下:
var a = 1,
reset = [2, 3, 4, 5];
console.log(a, reset); //1,[2,3,4,5]
注意:如果剩余参数是对应的值为 undefined,则赋值为[],因为找不到对应值的时候,是通过 slice 截取的,如下:
let [a, ...reset] = [1];
console.log(a, reset); //1,[]
其转成 ES5 的原理如下:
var _ref = [1],
a = _ref[0],
reset = _ref.slice(1);
console.log(a, reset); //1,[]
一条原则:要解构成数组的前提:如果等号右边,不是数组(严格地说,不是可遍历的解构),则直接报错,例如:
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]; //报错
先执行 new Set()去重,然后对得到的结果进行解构
let [a, b, c] = new Set([1, 2, 2, 3]);
console.log(a, b, c); //1,2,3
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
当变量严格等于 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
二、对象的解构赋值
let p1 = {
name: "zhuangzhuang",
age: 25
};
let { name, age } = p1; //注意变量必须为属性名
console.log(name, age); //"zhuangzhuang",25
其转成 es5 的原理则为:
var _p1 = p1,
name = _p1.name,
age = _p1.age;
console.log(name, age); //"zhuangzhuang",25
如果使用别名,则不允许再使用原有的解构出来的属性名,看以下举例则会明白:
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 等。
有些情况下,我们解构出来的值并不存在,所以需要设定一个默认值,例如:
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"
当我们并不是需要取出所有的值的时候,其实可以省略一些变量,这就是省略赋值,如下
let arr = [1, 2, 3];
let [, , c] = arr;
console.log(c); //3
注意:省略赋值并不存在与对象解构,因为对象解构,明确了需要的属性
let obj = {
name: "zhuangzhuang",
age: 27,
gender: "man"
};
let { age } = obj;
console.log(age); //27
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,可定报错
当我们写解构赋值的时候,很容易犯一个错误——{}的作用是块还是对象混淆,举例如下:
//举例一:
let {a} = {a:"a"};
console.loh(a);//'a',这个很简单
//很多人觉得,以下这种写法也是可以的:
let a;
{a} = {a:"a"};//直接报错,因为此时a已经声明过了,在语法解析的时候,会将这一行的{}看做块结构,而“块=对象”,显然是语法错误,所以正确的做法是不将大括号写在开头,如下:
let a;
({a} = {a:"a"})
按照之前写的,解构赋值,左边则为解构出来的属性名,当然,在这里,我们也可以不写任何属性名称,也不会又任何的语法错误,即便这样没有任何意义,如下:
({} = [true, false]);
({} = "abc");
({} = []);
如果解构成对象,右侧不是 null 或者 undefined 即可! 之前说过,要解构成数组,右侧必须是可迭代对象,但是如果解构成对象,右侧不是 null 活着 undefined 即可!
三、字符串的解构赋值
字符串也是可以解构赋值的
const [a, b, c, d, e] = "hello";
console.log(a, b, c, d, e); //'h','e','l','l','o'
转成 es5 的原理如下:
var _hello = "hello",
a = _hello[0],
b = _hello[1],
c = _hello[2];
console.log(a, b, c);
注意:字符串有一个属性 length,也可以被解构出来,但是要注意,解构属性一定是对象解构
let { length } = "hello";
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 无法转为对象,所以对它们进行解构赋值,都会报错。
- 解构成数组,等号右边必须为可迭代对象