以前给变量赋值都是var a = 10这样直接赋值,如今ES6提供了一种方法来赋值,即解构赋值,解构赋值是通过按照一定的模式,从数组和对象里读取值,然后对变量赋值。变量的解构赋值包括:数组,对象,字符串,数值,布尔值,函数参数。
【1】数组的解构赋值
var [a , b , c] = [1 , 2 , 3];
在ES6中可以直接这么对变量赋值,这种写法被称为“模式匹配”,只要两边的模式一样就可以进行赋值。例如:
let [a,[b,c]] = [1,[2,3]]; // a = 1 // b = 2 // c = 3 let [ , , c] = [1,2,3]; // c = 3 let [a, , c] = [1,2,3]; // a = 1 c = 3 let [a,...b] = [1,2,3]; // a = 1 ...b = [2,3] let [a,b,...c] = [1]; //a = 1 b = undefined ...c = []
上面最后一个赋值,b的值为undefined,这就是解构不成功的结果。
如果等号的右边不是数组,那么将会报错,譬如:
let [a] = 1; let [a] = false; let [a] = undefined; let [a] = null; let [a] = NaN; let [a] = {};
会报错是因为右边的值转为对象后不具有Iterator接口(前五个例子),还有本身就不具有Iterator接口(最后一个例子)。这说明了只要某种数据结构具有Iterator接口,都可以使用数组形式进行解构赋值。
解构赋值允许指定默认值,不过需要注意的是,只有变量严格等于undefined,默认值才会生效。比如:
var [x = 1] = [undefined]; // x =1 var [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
【2】对象的解构赋值
对象的解构与数组有一个重要的不同。即数组的元素是按次序排列的,变量的取值由它的位置所决定;而对象的属性不需要按次序排列,但需要注意的是,变量必须与属性同名,只有这样才能取到正确的值。比如:
var { bar, foo } = { foo: "aaa", bar: "bbb" }; // foo = "aaa" bar = "bbb" var { baz } = { foo: "aaa", bar: "bbb" }; // baz = undefined
如果变量名与属性名不一样,但又想取到值,可以这么写:
var { foo: baz } = { foo: 'aaa', bar: 'bbb' }; // baz = "aaa" // foo error: foo is not defined
通过这个例子,我们可以知道其实真正被赋值的是变量baz,而不是模式foo,也就是说对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。不过需要注意的是,采用这种写法时,变量的声明和赋值是一体的,对于let和const来说,变量是不能重新声明的,所以一旦赋值的变量以前声明过,就会报错。例如:
let foo; let {foo} = {foo: 1}; // SyntaxError: Duplicate declaration "foo"
不过如果变量是var 声明的就不会出现这个错误,因为var声明的变量允许重复声明,在去掉第二个let 之后就可以成功解构,例如:
let foo; ({foo} = {foo: 1}); // 成功
但是一定要记得加上(),这个是必须的,因为解析器会将起首的大括号,理解成一个代码块,而不是赋值语句。所以将一个已经声明的变量用于解构赋值就需要添加()。
解构也可以用于嵌套结构的对象。比如:
var node = { loc: { start: { line: 1, column: 5 } } }; var { loc: { start: { line }} } = node; line // 1 loc // error: loc is undefined start // error: start is undefined
只有line是变量,所以可以被赋值,而loc和start是模式,所以不会被赋值。
对象的解构也可以指定默认值,和数组一样,默认值生效的前提是,对象的属性值严格等于undefined。
【3】字符串的解构赋值
当使用字符串进行解构赋值时,字符串就被转换成了一个类似数组的对象,这个对象有一个length属性,也可以对这个属性解构赋值。比如:
const [a, b, c, d, e] = 'hello'; // a = "h" b = "e" c = "l" d = "l" e = "o" let {length : len} = 'hello'; // len = 5
【4】 数值和布尔值的解构赋值
当解构赋值数值和布尔值时,会先转化为对象。那么从这里我们就知道解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。注意undefined和null不能转为对象,所以无法对它们进行解构赋值,不然会报错,例如:
let { prop: x } = undefined; // TypeError let { prop: y } = null; // TypeError
【5】 函数参数的解构赋值
函数的参数不仅可以解构赋值,还可以使用默认值。而且undefined会触发函数参数的默认值。
function move({x = 0, y = 0} = {}) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, 0] move({}); // [0, 0] move(); // [0, 0]
如果是为函数move的参数指定默认值,而不是为变量x和y指定默认值,那么就会得到不一样的结果,如:
function move({x, y} = { x: 0, y: 0 }) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]
圆括号问题
在解构赋值的某些情况下,我们需要使用到圆括号,那么在什么情况下可以用圆括号,什么情况下不能用圆括号。
可以使用圆括号:[1]: 赋值语句的非模式部分,可以使用圆括号。
不可以使用圆括号:[1]:变量声明语句中,不能带有圆括号。
[2]:函数参数中,模式不能带有圆括号。
[3]:赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中。
变量的解构赋值在什么时候用呢——[1]:交换变量的值;
[2]:从函数返回多个值;
[3]:函数参数的定义;
[4]:提取 JSON 数据;
[5]:函数参数的默认值;
[6]:遍历 Map 结构;
[7]:输入模块的指定方法。