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

125 阅读7分钟

数组的赋值解构

先来看一组解构赋值:

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]