ES6学习 —— 变量的解构赋值

本文是学习 阮一峰《ECMAScript 6 入门》 变量的解构赋值 部分的笔记。

解构(Destructing): ES6 按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称为 “解构” 。

数组的解构赋值

基本用法

可以从数组中提取值,按照对应的位置,对变量赋值;

“模式匹配”: 只要等号两边的模式相同,左边的变量就会被赋予对应的值;

1
2
3
4
let [a, b, c] = [1, 2, 3];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

1、使用嵌套数组进行解构

1
2
3
4
5
6
7
8
9
10
11
let [a, [[b], c]] = [1, [[2], 3]];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
let [ , , third] = [1, 2, 3];
console.log(third); // 3
let [first, ...last] = [1,2,3,4];
console.log(first); // 1
console.log(last); // [2,3,4]

2、解构不成功
如果解构不成功,变量的值就等于 undefined(等号右边的模式,只匹配等号左边的一部分)

1
2
3
let [x,y, ...z] = [1];
console.log(y); // undefined
console.log(z);

3、不完全解构
等号左边的模式,只匹配等号右边一部分的数组,这种情况下依然可以解构成功,但是不完全解构。

1
2
3
4
5
6
7
8
let [a,b] = [1,2,3];
console.log(a); // 1
console.log(b); // 2
let [x,[y],z] = [1,[2,3],4];
console.log(x); // 1
console.log(y); // 2
cosnole.log(z); // 4

4、等号右边不是数组:报错!
严格的说,应该是不可遍历的解构

1
2
3
4
5
6
7
8
let [a] = 1;
let [b] = false;
let [c] = NaN;
let [d] = undefined;
let [e] = null;
let [f] = {};
// 以上代码均会报错
// 原因:等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个),要么本身就不具备 Iterator 接口(最后一个)。

对于 Set 结构(后面内容),也可以使用数组的解构赋值;

1
2
let [x,y,z] = new Set(['a','b','c']);
console.log(x); // 'a'
只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值;

默认值

解构赋值允许指定默认值;

1
2
3
4
let [a = true] = [];
console.log(a); // true;
let[x,y = 2] = [1]; // x=1, y=2
let[x,y = 2] = [1,undefined] // x=1, y=2

1、不生效
ES6 内部使用严格相等运算符(===)来判断一个位置是否有值。所以一个数组成员不严格等于 undefined ,默认值是不会生效的。

1
2
let [a = 1] = [undefined]; // 生效,a = 1
let [b = 2] = [null]; // 不生效,b = null

2、表达式
如果默认值是一个表达式,则这个表达式是惰性求值的,即只有在用到的时候,才会求值。

1
2
3
4
5
function f(){
console.log('a');
}
let [x = f()] = [1]; // x=1

上面代码中,x 能取到值 (1),所以函数 f() 不会执行。

3、引用解构赋值的其他变量

默认值可以引用结构赋值的其他变量,但是该变量必须已经声明。

1
2
3
4
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] = []; // Uncaught ReferenceError: y is not defined

对象的解构赋值

1、与数组的不同

数组的元素是按次序排列的,变量的取值由它们的位置决定;

而对象的属性没有次序,变量必须与属性同名,才能取到正确的值;

1
2
3
4
5
6
7
8
9
let {name, age, hobby} = {age: 23, name:"Bob", interest: "running"};
console.log(name); // Bob
console.log(age); // 23
console.log(hobby); // undefined
let{name:name, age:age, hobby:interest} = {age:23, hobby:"running", name:"Bob"};
console.log(name); // Bob
console.log(age); // 23
console.log(hobby); // running

第一个例子:前两个变量(nameage),虽然和等号右边的顺序不一样,但是因为等号右边有两个同名属性,所以依然可以取到值;而最后一个变量(hobby),等号右边不存在同名属性,所以取不到值。

第二个例子:这是对象解构赋值最完整的写法,即等号左边(hobby:interest),其中 hobby 是匹配的模式, interest 才是变量。

等号右边(interest:“running”), hobby 是变量的同名属性(其实是属性与匹配模式同名),“running” 是值。

看下面两个例子,更好的理解同名属性具体指的是什么。

1
2
3
4
5
6
7
let {name:who, age: how} = {name:"Bob", age:25};
console.log(who); // Bob
console.log(how); // 25
let {name:who, age: how} = {who:"Bob", how:25};
console.log(who); // undefined
console.log(how); // undefined

概括一下:对象的解构赋值,首先要找到变量的同名属性,然后再赋值给对应的变量,真正被赋值的是后者,而不是前者。(比如 name:name,真正被赋值的是后面的 name ,而不是前面的 name)。

2、嵌套解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
country:{
person:{
name: "Bob",
age: 25
}
}
};
let {country, country:{person}, country:{person:{age}}} = obj;
console.log(country); // Object {person: Object}
console.log(person); // Object {name: "Bob", age: 25}
console.log(age); // 25

上面代码中,一共有三次解构赋值,先看最后一次 country:{person:{age}}countryperson 在这里都是模式,不是变量,所以只有变量 age 能取到值;如果想要让模式 countryperson 也要作为变量取到值,就要像前面两次一样。

嵌套赋值

1
2
3
4
5
6
let obj = {};
let arr = [];
({foo: obj.prop, bar: arr[0]} = {foo: 123, bar: true});
console.log(obj); // Object {prop: 123}
console.log(arr); // [true]

3、指定默认值

与数组一样,默认值生效的条件是,对象的属性值严格等于 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var {x = 3} = {x: undefined};
console.log(x); // 3
var {x = 3} = {x: null};
console.log(x); // null
var {x, y=2} = {x:1};
console.log(x); // 1
console.log(y); // 2
var {x: y=3} = {};
console.log(y); // 3
var {x: y=3} = {x:1};
console.log(y); // 1

4、解构失败

如果解构失败,变量的值等于 undefined

1
2
3
4
let {name} = {age:25};
console.log(name); // undefined
let {person: {name}} = {age:25}; //报错

上面第二个例子,等号左边对象 person 模式对应的变量还是一个对象,进行解构赋值时, person 等于 undefined,再去子属性就会报错。

因此,如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,就会报错。

5、已经声明的变量用于解构赋值(Be careful!)

1
2
3
4
5
6
7
// 错误的写法
let x;
{x} = {x:1}; // Uncaught SyntaxError: Unexpected token =
// 正确的写法
let x;
({x} = {x:1});

如果是已经声明的一个变量,用于解构赋值时,{x} 会被理解为代码块,从而发生语法错误。此时,应该要避免 {x} 被理解为代码块,可以在最外面加上一个圆括号,如第二个例子,就不会报错。

6、其他

解构赋值允许等号左边的模式之中,不放置任何变量名,虽然语法是合理的,但是毫无意义;

1
2
3
({} = [true, false]);
({} = 'abc');
({} = []);

对象的解构赋值,可以很方便的将现有对象的方法,赋值给某个变量;

1
let {log, sin, cos} = Math;

这样每次使用Math对象的对数、正弦、余弦方法时,只要直接调用 logsincos 即可。

可以数组进行对象属性的解构,因为数组本质是特殊的对象;

1
2
3
4
let arr = [1,2,3];
let {0:first, [arr.length -1]:last} = arr;
console.log(first); // 1
console.log(last); // 3

上面第二行代码扩展开来其实就是:

1
let {0:first, [arr.length -1]:last} = {0:1, 1:2, 2:3};

等号右边,每一项冒号的左边其实就是数组的索引值。

您的支持将鼓励我继续创作!