数组的赋值解构
先来看一组解构赋值:
let [a, b, c] = [1, 2, 3];
你觉得答案是怎样的呢?~~~~~~~
登登登登,答案揭晓:a=1,b=2,c=3。
下面我们来看几个嵌套数组来解构的例子:
let [foo, [[bar], baz]] = [1,[[2], 3]];
console.log('foo = ' + foo); // foo = 1;
console.log('bar = ' + bar); // bar = 2;
console.log('baz = ' + baz); // baz = 3;
let [, , third] = ['foo', 'bar', 'baz'];
console.log('third = ' + third); // third = baz;
let [x, , y] = [1, 2, 3];
console.log('x = ' + x); // x = 1;
console.log('y = ' + y); // y = 3;
let [head, ...tail] = [1, 2, 3, 4];
console.log('head = ' + head); // head = 1;
console.log('tail = ' + tail); // tail = [2, 3, 4];
let [a, b, ...c] = ['a'];
console.log('a = ' + a); // a = 'a';
console.log('b = ' + b); // b = undefined;
console.log('c = ' + c); // c = [];
如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。
如果解构不成功,变量的值就等于undefined。 栗子君正在向你奔来~~~~
let [foo] = [];
console.log(foo); // undefined
let [bar, fox] = [1];
console.log(bar); // 1
console.log(fox); // undefined
默认值
注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。
如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。
栗子君到达战场,哼哼o( ̄ヘ ̄o#)
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
let [z = 1] = [null];
console.log(z); // null
如果默认值是一个表达式,那么这个表达式==是惰性求值==的,即只有在用到的时候,才会求值。
function f() {
console.log('aaa');
}
let [x = f()] = [1];
console.log(x);
上述代码,==因为x能取到值,所以函数f根本不会执行。==
默认值可以引用解构赋值的其他变量,但该==变量必须已经声明==。
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] = [];
console.log(x); // ReferenceError: y is not defined
console.log(y);
最后一个栗子为什么会出错?是因为y还没有声明,所以会报错啦!
对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;==而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。==
小栗子来也!!!
let {foo, bar} = {foo: 'foo', bar: 'bar'};
console.log('foo = ' + foo); // foo = foo
console.log('bar = ' + bar); // bar = bar
let {baz} = {foo: 'foo1', bar: 'bar2'};
console.log('foo = ' + foo); // foo = foo
console.log('bar = ' + bar); // bar = bar
console.log('baz = ' + baz); // baz = undefined
上面的栗子里,baz变量没有对应的属性,所以其值是undefined。而将'foo1'重新赋给foo变量时 ,foo的值却还是'foo',这是因为在第二个let语句里没有指定foo变量,所以值还是前一个值。
如果变量名与属性名不一致,必须写成下面这样。
let {foo: baz} = {foo: 'aaa', bar: 'bbb'};
console.log('baz = ' + baz); // baz = 'aaa'
console.log('foo = ' + foo); // error: foo is not defined
let obj = {first: 'hello', second: 'world'};
let {first: f, second: s} = obj;
console.log('f = ' + f); // f = 'hello'
console.log('s = ' + s); // s = 'world'
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。也就是说上面的第一个栗子,我们真正赋值的是baz而不是foo,foo是匹配的模式,baz才是真正的变量。
解构嵌套解构的对象
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
console.log(x); // Hello
console.log(y); // World
console.log(p); // ReferenceError: p is not defined
注意,这时p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p, p: [x, { y }] } = obj;
console.log(x); // Hello
console.log(y); // World
console.log(p); // Hello: { 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: { start } —— loc是模式,start是变量
- loc: { start: { line } } } —— loc, start是模式,line是变量
对象的解构也可以指定默认值
var { a = 3 } = {};
console.log('a = ' + a); // a = 3;
var { b, c = 5 } = { b: 1 };
console.log('b = ' + b); // b = 1;
console.log('c = ' + c); // c = 5;
var { d: e = 3 } = {};
console.log('e = ' + e); // e = 3;
var { f: g = 3 } = { f : 5 };
console.log('g = ' + g); // g = 5;
var { message: msg = 'something went wrong' } = {};
console.log('msg = ' + msg); // msg = something went wrong;
== 默认值生效的条件是,对象的属性值严格等于undefined。==
var { x = 3 } = { x : undefined };
console.log(x); // 3
var { y = 3 } = { y : null};
console.log(y); // null
==如果解构失败,变量的值等于undefined。==
let { foo } = { bar: 'baz' };
console.log(foo); // undefined
==如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。==
let { foo: { bar } } = { baz: 'baz' };
console.log(bar); // Error: Cannot destructure property `bar` of 'undefined' or 'null'.
上面代码中,等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错。
如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误写法
let x;
{ x } = { x: 1 };
// 正确写法
let x;
({ x } = { x: 1 });
为什么第一种写法会报错?这是因为JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。
正确的写法是将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。关于圆括号与解构赋值的关系。
==由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。==
let arr = [1, 2, 3];
let {0: first, [arr.length - 1]: last} = arr;
console.log(first); // 1
console.log(last); // 3
上面的代码中,索引[0]对应的值为1,对应的变量为first;索引[arr.length-1]对应的值为3,对应的变量为last。==方括号这种写法,属于“属性名表达式”。==
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
console.log(a); // h
console.log(b); // e
console.log(c); // l
console.log(d); // l
console.log(e); // o
类似数组的对象都有一个==length属性==,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
console.log(len); // 5
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值或布尔值,会先解转化为对象。
let { toString: s } = 123;
console.log(s === Number.prototype.toString); // true
let { toString: v } = true;
console.log(v === Boolean.prototype.toString); // true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: a } = undefined;
let { prop: b } = null;
console.log(a); // Error: Cannot destructure property `prop` of 'undefined' or 'null'.
console.log(b); // Error: Cannot destructure property `prop` of 'undefined' or 'null'.
函数参数的解构赋值
function add([x, y]) {
console.log(x + y);
}
add([1, 2]);
上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。 又一个栗子君来袭~~~
[[1, 2], [3, 4]].map(([a, b]) => console.log(a + b)); // [3, 7]
函数参数的解构赋值也可以用默认值:
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的值。如果解构失败,x和y等于默认值。
注意,下面的写法会得到不一样的结果。
function move1({x, y} = {x: 0, y: 0}) {
return [x, y];
}
move1({x: 3, y: 8}); // [3, 8]
move1({x: 3}); // [3, undefined]
move1({}); // [undefined, undefined]
move1(); // [0, 0]
上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。
undefined就会触发函数参数的默认值。
[1, undefined, 3].map((x = 'yes') => x); // [1, 'yes', 3]