ES6中解构赋值主要分为6类,分别为 数组解构赋值 、对象解构赋值 、字符串解构赋值 、数值和布尔值解构赋值 、函数参数解构赋值 。
什么是解构?
在ES6中允许按照一定的模式从数组和对象中提取值,然后对变量进行赋值,这被称为解构(Destructuring)
数组解构赋值
数组的解构赋值时,等号的右边必须是数组,否则将会报错。只要数据结构具有Iterator借口,则都可以采用数组形式的解构赋值。
let [a, b, c] = [1, 2, 3];
a // 1
b // 2
c // 3
这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些常见的解构例子:
let [f, [[k], y]] = [1, [[2], 3]];
f // 1
k // 2
y // 3
let [ , ,t] = ["php", "java", "go"];
t // go
let [x, ,y] = ["php", "java", "go"];
x // php
y // go
let [k, ...t] = [1, 2, 3, 4];
k // 1
t // [2, 3, 4]
let [x, y, ...z] = ["php"];
x // php
y // undefined
z // []
当解构不成功,变量的值就等于undefined。下面代码中,f均解构不成功,为undefined。
let [f] = [];
let [k, f] = [1];
Set结构也可以使用数组的解构赋值,如下:
let [x, y, z] = new Set(["php", "java", "go"]);
x // php
y // java
z // go
还有一种情况是部分解构成功,如下:
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], c] = [1, [2, 3], 4]
a // 1
b // 2
c // 4
以上情况是不完全解构,即等号左边的模式只能匹配一部分等号右边的数组。但是这种情况下,结构依然可以成功。
let [f] = 1;
let [f] = false;
let [f] = NaN;
let [f] = undefined;
let [f] = null;
let [f] = {};
以上语句都会报错,因为等号右边的值或是转为对象以后不具备iterator接口(前五个表达式),或是本事就不具备Iterator接口(最后一个表达式)
数组解构赋值中允许指定 默认值 ,如:
let [f = true] = [];
f // true
let [a, b = 'x'] = ['y'];
a // y
b // x
let [a, b = 'c'] = ['k', undefined];
a // k
b // c
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
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] = []; // 报错:ReferenceError
最后一个表达式之所以报错,是因为x引用了默认值y时,y还没有被声明。
注:ES6内部使用严格相等运算符(===)判断一个位置是否有值。如果一个数组成员不严格等于undefined,默认值是无法生效的。
对象解构赋值
对象的解构与数组有一个重要的不同:数组的元素是按次序排列的,变量的取值是由它的位置决定的;而对象的属性没有次序,变量必须与属性同名才能取到正确的值。示例如下:
let {f, b} = {f: "AA", b: "KK"}; // f --> AA b --> KK
let {c} = {f: "AA", b: "KK"}; // c --> undefined
如果变量名与属性名不一致,必须用如下的形式:
let {f: b} = {f: 'AA', b: 'CC'}; // b --> AA
let obj = {hello: 'AA', world: 'CC'};
let {hello: h, world: w} = obj; // h --> hello w --> world hello --> error: hello is not defined
对象的解构赋值的内部机制是先找到同名属性,然后再赋值给对应的变量。真正被赋值的是后者,而不是前者。
对象解构也可以指定默认值:
let {x = 3} = {}; // x --> 3
let {x, y = 5} = {x: 1}; // x --> 1 y --> 5
let {x : y = 3} = {}; // y --> 3
let {x : y = 3} = {y : 5}; // y --> 5
let {x = 3} = {x : undefined}; // x --> 3
let {x = 3} = {x : null}; // x --> null
默认值生效的条件是对象的属性值严格等于undefined
如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错,如下:
let {f: {bo}} = {bc: "bucc"}
以上代码报错的原因是因为f此时等于undefined,再取子属性就会报错。
如果将一个已经声明的变量用于解构赋值,必须非常小心。 常见的错误如下:
// 错误的写法
let x;
{x} = {x : 1}; // SyntaxError: syntax error
// 正确的写法
let x;
({x} = {x : 1});
以上代码中第一部分会报错,是因为JavaScript引擎会将{ x }
理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。
解构赋值允许等号左边的模式之中不放置任何变量名。虽然此类表达式毫无意义,但是语法合法,且可以正常执行:
({} = [true, false]);
({} = 'abc');
({} = []);
由于数组本质是特殊的对象,因为可以对数组进行对象属性的解构:
let arr = [1, 2, 3];
let {0: first, [arr.length - 1]: last} = arr; // first --> 1 last --> 3
字符串解构赋值
字符串在进行解构赋值时,字符串被转换成了一个类似数组的对象:
let [a, b, c, d, e] = 'hello'; // a -->h a -->e a -->l a -->l a -->o
类似数组的对象都有一个length的属性,因为还可以对这个属性进行解构赋值:
let {length : len} = 'hello'; // len --> 5
数值和布尔值解构赋值
在数值和布尔值解构赋值时,如果等号右边是数值或布尔值,则会先转为对象。
let {toString:s} = 123;
s === Number.prototype.toString; // true
let {toString:s} = true;
s === Boolean.prototype.toString; // true
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。
let {prop: x} = undefined; // TypeError
let {prop: y} = null; // TypeError
函数参数解构赋值
函数的参数也可以使用解构赋值。
function add([x, y]) {
return x + y;
}
add([3, 2]); // 5
[[1,2],[3,4]].map(([a, b]) => a + b); // [3, 7]
函数参数的解构也可以使用默认值:
// 示例一
function move ({x = 0, y = 0} = {}) {
return [x, y]
}
move({x: 3, y: 9}); // [3, 9]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
// 示例二
function move({x, y} = {x : 0, y : 0}) {
return [x, y];
}
move({x: 3, y: 9}); // [3, 9]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
关于圆括号的问题
解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。
ES6规则是只要有可能导致解构的歧义,就不得使用圆括号。但是这个在实际中并不那么容易判断,且处理起来相当的麻烦。所以也建议,只要有可能就不要在模式中放置圆括号。
不能使用圆括号的情况
情况一:变量声明语句
let [(a)] = [1]; // 错误
let {x : (c)} = {};// 错误
let ({x : c}) = [1];// 错误
let {(x : c)} = [1];// 错误
let {(x) : c} = [1];// 错误
let {o : ({p:p})} = {o: {p: 2}};// 错误
类似以上代码语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。
情况二:函数参数
函数参数也属于变量声明,因此不能使用圆括号
function f ([(z)]) { return z; }// 错误
function f ([z, (x)]) {return x; }// 错误
情况三:赋值语句的模式
// 将整个模式放入圆括号之中导致报错
({p : a}) = {p: 4};// 错误
([a]) = [5];// 错误
// 将一部分模式放入圆括号之中导致报错
[({p: a}), {x: c}] = [{}, {}];// 错误
可以使用圆括号的情况
可以使用圆括号的情况只有一种:赋值语句的非模式部分可以使用圆括号。
[(b)] = [3]; // 正确
({p: (d)} = {});// 正确
[(parseInt.prop)] = [3];// 正确
以上三个语句均执行正确,因为他们的都是赋值语句,而不是声明的语句,另外它们的圆括号都不属于模式的一部分。
用途
变量的解构赋值用途很多:
交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里面返回。
// 返回一个数组
function test1() {
return [1, 2, 3];
}
let [a, b, c] = test1();
// 返回一个对象
function test2() {
return {
a: 2,
b: 6
};
}
let {a, b} = test2();
函数参数的定义
解构赋值可以方便的将一组参数与变量名对应起来
// 参数是一组有序的值
function f1([x, y, z]) { ... }
f1([1, 2, 3]);
// 参数是一组无序的值
function f2({x, y, z}}) { ... }
f2({z: 8, x: 6, y: 4});
提取JSON数据
let jsonData = {
id: 2,
name: 'LiMing',
data: [867, 22]
}
let {id, name, data: nums} = jsonData;
console.log(id, name, nums);
// 2, "LiMing", [867, 22]
函数参数的默认值
jQuery.ajax = function (url, {
async = true,
cache = true,
complate = function(){}
}) {
// do stuff
}
遍历Map结构
任何部署了Iterator接口的对象都可以用for...of 循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值获取键名和键值就非常方便。
let map = new Map();
map.set('one', 'hello');
map.set('two', 'world');
for(let [key , value] of map) {
console.log(key, value);
}
// 获取键名
for(let [key] of map) {
console.log(key);
}
// 获取键值
for(let [, value] of map) {
console.log(value);
}
输入模块的指定方法:
加载模块时往往需要指定输入的方法。解构赋值使得输入语句非常清晰:
const {SourceMapConsumer, SourceNode} = require("source-map");
评论