目录
文章目录
解构
定义
解构赋值是在ES6中,允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
数组的解构赋值
基本用法
- 之前赋值用法,只能直接指定值
let a = 1;
let b = 2;
let c = 3;
- 在ES6中可以这样写:
let [a, b, c] = [1, 2, 3];
上面的代码可以从数组中提取值,按照对应的位置,对变量赋值。本质上,这种写法属于“模式匹配”,只要等号两边的模式一样,左边的变量就会被赋予对应的值。
嵌套使用
// 嵌套数组
let [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo); // 1
console.log(bar); // 2
console.log(baz); // 3
let [,,third] = [1,2,3];
console.log(third); // 3
let [x, , y] = [1,2,3];
console.log(x); // 1
console.log(y); // 2
let [head, ...tail] = [1,2,3,4];
console.log(head); // 1
console.log(tail); // [2,3,4]
let [x,y,...z] = ['a'];
console.log(x); // a
console.log(y); // undefined
console.log(z); // []
- 如果解构不成功,那么变量的值就是
undefind
.
不完全解构
当等号左边只能匹配等号右边的一部分的数组,这种情况下依旧可以成功解构。
let [x,y] = [1,2,3]
console.log(x); // 1
console.log(y); // 2
let [a, [b], d] = [1, [2,3],4];
console.log(a); // 1
console.log(b); // 2
console.log(d); // 4
上面都属于不完全解构,但是都可以解构成功。
- 如果等号右边不是数组(或者是不可以遍历的结构),那么将会报错。
let [foo] = 1; //Type '1' is not an array type.
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {}; // Type '{}' is not an array type.
上面都会报错。
- 实质上只要某种数据结构具有
Iterato
的接口,都可以采用数组形式的解构赋值。
设置默认值
解构赋值允许指定默认值
let [foo = true] = [];
console.log(foo); // true
let [x, y='b'] = ['a'];
console.log(x); // 'a'
console.log(y); // 'b'
let [x, y='b'] = ['a', undefined];
console.log(x); // 'a'
console.log(y); // 'b'
在ES6内部使用严格相等(===
)来判断一个值是否有效。所以在设置某一个值严格为undefined
时,默认值才会生效。
let [x = 1] = [undefined];
console.log(x); // 1
let [x = 1] = [null];
console.log(x); // null
- 如果默认值是一个表达式,那个这个表达式是惰性求值的,只用在用到的时候才会求值。
function f() {
console.log('aaa');
}
let [x = f()] = [1];
console.log(x); // 1
因为这里的x
是可以取到值为1的,所以这个的f()
是不会执行的。
function f() {
console.log('aaa');
}
let [x = f()] = [];
console.log(x); // undefined
因为这里的x
已经取不到值了,所以这里的f()
会执行,但是由于f()
没有任何的返回值,所以这里会在控制台上输出aaa
,但是x
的值为undefined
.
在下面这种情况时,x
是可以取到值的:
function f() {
return 3
}
let [x = f()] = [];
console.log(x); // 3
- 默认值可以引用解构赋值的其他变量,但是在引用前,要引用的变量已经声明。
let [x = 1, y = x] = [] ;
console.log(x); // 1
console.log(y); // 1
let [x = 1, y = x] = [2] ;
console.log(x); // 2
console.log(y); // 2
let [x = 1, y = x] = [1,2] ;
console.log(x); // 1
console.log(y); // 2
let [x = y, y = 1] = [] ; // 报错
最后一个报错是因为,x
在引用y
的时候,y
还没有声明。
对象的解构赋值
对象的解构赋值和数组解构赋值的区别
- 数组解构赋值的元素是按次序排列的,变量的取值由它的位置决定
- 对象解构赋值的属性没有次序,变量必须和属性同名,才能取到正确的值
基本用法
let { bar, foo}={foo:'aaa',bar:'bbb'};
console.log(bar); // 'bbb';
console.log(foo); // 'aaa';
let { baz } = { foo: 'aaa', bar: 'bbb' };
console.log(baz); // undefined
-
如果解构失败,那么变量的值就是
undefined
. -
对象的解构赋值,可以很方便的将现有的对象赋值给某一个变量。
// 例一
let { log, sin, cos } = Math;
// 例二
const { log } = console;
log('hello') // hello
- 第一个例子是将
Math
中的log
,sin
,cos
这三个方法赋值到了对应的变量上,使用起来就非常方便了. - 第二个例子是将
console.log
赋值给了log
变量.
- 对象的解构赋值是对象的一种简写:
let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };
- 对象的解构赋值的内部机制是先找到同名属性,然后再赋值给对应的变量,真正被赋值的是后者不是前者。
let { foo: baz, } = { foo: 'aaa', bar: 'bbb' };
console.log(baz); // 'aaa';
console.log(foo); // error: foo is not defined
在这里foo
是匹配模式,baz
才是真正的变量。真正被赋值的是变量baz
,而不是匹配模式foo
.
嵌套使用
与数组一样,对象解构赋值也可以嵌套。
let obj = {
p:[
'Hello',
{y:'World'}
]
};
let { p: [x, { y }] } = obj;
console.log(x); // 'Hello'
console.log(y); // 'World'
注意这里的
p
是匹配模式,不是变量。
如果要让p
也是变量,需要如下写:
let obj = {
p:[
'Hello',
{y:'World'}
]
};
let {p, p: [x, { y }] } = obj;
console.log(p); // ['Hello',{y:'World'}]
console.log(x); // 'Hello'
console.log(y); // 'World'
复杂的嵌套
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
}
let {loc, loc: { start }, loc:{ start:{line}} } = node;
console.log(loc); // { start: { line: 1,column: 5 }}
console.log(start); // { line: 1,column: 5 }
console.log(line); // 1
对于上面的代码,第一个是对loc
属性的赋值,第二个loc
是匹配模式,对start
进行赋值,第三个loc
、start
都是匹配模式,对line
进行赋值。
- 当结构模式是嵌套的对象,而且子对象所在的父属性不存在,那么就会报错.
let {foo: { bar }} = {baz: 'baz'};
此时等号左边对象的foo
属性对应的是一个子对象,这个子对象中有一个bar
属性,解构的时候就会报错。因为这个时候取foo
的时候是undefined
,再undefined
的基础上取子属性就会报错。
- 对象的解构赋值可以取到继承的属性
const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1,obj2);
const {foo} = obj1;
console.log(foo); // 'bar'
对象obj1
的原型对象是obj2
。foo
属性不是obj1
自己的属性,而是继承于obj2
的属性,解构赋值是可以取到这个属性的。
设置默认值
对象的解构赋值也是可以设置默认值的。
var {x = 3} = {};
console.log(x); // 3
var {x, y = 5} = {x: 1 };
console.log(x); // 1
console.log(y); // 5
var {x : y = 3} = {};
console.log(y); // 3
var {x: y = 3} = {x: 5};
console.log(y); // 5
默认值产生的条件是对象的属性严格等于undefined
.
var {x = 3} = {x: undefined};
console.log(x); // 3
var {x = 3} = {x: null};
console.log(x); // null
需要注意
- 如果要将一个已经声明的变量用于解构赋值,需要非常小心大括号
{}
.
let x;
{x} = {x: 1};
此时,因为{}
会构成一个新的代码块,所以这里的代码就会报错,避免报错,需要按照下面的方式写(避免{}
位于行首):
let x;
({x} = {x: 1});
此时就不会报错,并且可以正常进行解构赋值。
- 解构赋值允许等号左边的模式之中,不放置任何变量名,所以就会出现很多奇怪的赋值表达式。
({} = [true, false]);
({} = 'abc');
({} = []);
虽然上面的表达式没有意义,但是是合法的。
- 由于数组本质是特殊的对象,所以可以用数组对对象属性进行解构赋值
let arr = [1,2,3];
let {0 : first, [arr.length - 1] : last} =arr;
console.log(first); // 1
console.log(last); // 3
字符串的解构赋值
字符串也可以解构赋值,此时的字符串被转换成了一个类似数组的对象,并且还有一个length
的属性,此时也可以根据这个属性进行属性解构赋值。
const [a,b,c,d,e] = 'hello';
let {length: len} = 'hello';
console.log(a); // 'h'
console.log(b); // 'e'
console.log(c); // 'l'
console.log(d); // 'l'
console.log(e); // 'o'
console.log(len); // 5
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值或者布尔值,则先转换成对象。
let {toString: s} = 123;
console.log(s); // function toString(){}
console.log(s === Number.prototype.toString); // true
let {toString: s} = true;
console.log(s === Boolean.prototype.toString); // true
只要等号的右边的值不是对象或者数,就先将其转换成对象。由于
undefined
和null
无法转换成对象,所以对其进行解构赋值会报错。
函数参数的解构赋值
基本用法
函数的参数也可以解构赋值。
function add([x,y]){
return x+y;
}
console.log(add([1,2])); // 3
上面add()
函数的参数表面上是一个数组,但是在传入参数的时候,数组的参数就被解构成了变量x
和y
.
设置默认值
function move({x = 0, y = 0} = {}){
return [x, y];
}
console.log({x: 3, y: 8}); // [3, 8]
console.log({x: 3}); // [3, 0]
console.log({}); // [0, 0]
console.log(); // [0, 0]
函数move()
的参数是一个对象,通过这个对象进行解构,得到变量x
和变量y
的值。如果解构赋值失败,则等于默认值。
需要注意
function move({ x, y } = { x: 0, y: 0 }){
return [x, y];
}
console.log({x: 3, y: 8}); // [3, 8]
console.log({x: 3}); // [3, undefined]
console.log({}); // [undefined, undefined]
console.log(); // [undefined, undefined]
上面是为函数move()
的参数设置了默认值,并不是为变量x
和y
指定默认值。所以回到了与之前不一样的结果。
undefined
会触发函数参数的默认值
[1, undefined, 3].map((x = 'yes') => x);
// [1, 'yes', 3];
圆括号的问题
对于编译器来说,必须解析到(或者解析不到)等号才能知道当前解析的是匹配模式还是表达式。
在ES6中,只要有可能导致解构歧义的就不得使用圆括号
不能使用圆括号的情况
变量声明语句
let [(a)] = [1];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};
let {o : ({p: p})} ={o : {p: 2}};
上面这六种都会报错,在声明变量语句中,匹配模式不能使用圆括号。
函数参数
函数参数也属于变量声明,所以不能带圆括号。
function f([(z)]) {return z;} // 报错
function f([z, (x)]) {return x;} // 报错
赋值语句的模式
({p: a}) = {p:42}; // 报错
([a]) = [5]; // 报错
[({p:1}), {x:c}] =[{},{}]; // 报错
不能将整个模式放在圆括号之中,也不能将部分模式放在圆括号之中。
可以使用圆括号的情况
赋值语句的非模式部分,可以使用圆括号
[(b)] = [3];
({p: (d)}) = {};
[(parsnInt.prop)] = [3];
用途
交换变量的值
let x = 1;
let y = 2;
[x,y] = [y,x];
console.log(x);
console.log(y);
上面代码交换变量x
和y
的值,这样写简单、易读、语义清晰。
从函数返回多个值
- 返回一个数组
function example(){
return [1,2,3];
}
let [a,b,c] = example();
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
- 返回一个对象
function example(){
return {
foo: 1,
bar: 2,
};
}
let { foo, bar } = example();
console.log(foo); // 1
console.log(bar); // 2
函数参数的定义
解构赋值可以方便的将一组参数与变量名对应起来
- 参数是一组有序的值
function f([x,y,z]){ ... }
f([1,2,3]);
- 参数是一组无序的值
function f({x,y,z}){ ... }
f({z:3,y:2,x:1});
提取 JSON 数据
let jsonData={
id: 42,
status: 'ok',
data: [555,333]
};
let { id, status, data: number } = jsonData;
console.log(id); // 42
console.log(status); // 'ok'
console.log(number); // [555,333]
函数参数的默认值
function ({x = 1, y = 2, z = 3} = {}){}
指定函数饿默认值,就可以避免函数体内部在写判断语句。
遍历 Map 解构
任何具有 Iterator
接口的对象,都可以使用for...of
循环遍历。配合变量的解构赋值,获取Map解构中的key
和value
就非常方便了。
const 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
遍历数组
const arr = [
{id: 1, name: 'a'},
{id: 2, name: 'b'},
{id: 3, name: 'c'},
];
for (const {id, name} of arr) {
console.log(`${id} is ${name}`);
}
// 1 is a
// 2 is b
// 3 is c
输入模块的指定方法
加载模块的时候,有时需要指定输入哪些方法。解构赋值可以使输入语句显得非常清晰。
const { SourceNode } = require('resource-map');
备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。