ES6学习笔记之解构赋值

Learning Card

  • 规则
    • 只要等号两边形式相同,就可以将右边的值赋予左边的变量
    • 有遍历接口的类型都可以解构赋值(字符串、数组、Set、generator函数…)
  • 数组的解构赋值
    • 完全解构 左边完全等同于右边
    • 不完全解构 左边形式仅为右边形式的一部分
  • 对象的解构赋值
    • 变量与属性同名,根据属性赋值
  • 默认值
    • 等号左边形式中给变量赋值
    • 对应变量的值严格等于undefined即可生效
  • 字符串的解构赋值
    • 转换成类似数组的对象
  • 数值和布尔值的解构赋值
    • 县转换为对象
  • 函数的参数也可以解构赋值
  • 常见用途
    • 交换变量的值
    • 函数返回多个值
    • 提取JSON数据
    • 遍历Map结构
    • 输入模块的指定方法

解构赋值是将数组或对象或其他有遍历接口的数据的一部分值提取出来,赋给另一个值的语法。用简单的话说,就是只要等号两边的形式相同,就可以将右边的值赋予左边的变量

常见的解构赋值多用于数组和对象,但实际上有遍历接口的类型都可以解构赋值,如es6新增加的数据结构Set也可以解构赋值:

1
2
3
4
let [x, y, z] = new Set(['a', 'b', 'c']);
console.log(x); //"a"
console.log(y); //"b"
console.log(z); //"c"

generator函数也可以解构赋值

1
2
3
4
5
6
7
8
9
10
function * fibs() {
let a = 0;
let b = 1;
while(true) {
yield a;
[a, b] = [b, a + b];
}
}
let [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth); //5

如果右边是不能遍历的结构,结果会报错:

1
2
3
4
5
6
let [foo] = 1;             //1 is not iterable
let [foo] = false; //false is not iterable
let [foo] = NaN; //NaN is not iterable
let [foo] = undefined; //undefined is not iterable
let [foo] = null; //null is not iterable
let [foo] = {}; //{} is not iterable

数组的解构赋值

如前面所说,只要等号两边形式相同,左边的变量就会被赋予相应的值。最典型的例子就是:

1
2
3
4
let [a, b, c] = [1, 2, 3];
console.log(a); //1
console.log(b); //2
console.log(c); //3

像上面这种左边形式完全等同于右边形式叫做完全解构,当左边形式仅为右边形式的一部分时,同样可以解构赋值,叫做不完全解构

1
2
3
let [a, b, c] = [1, 2, 3, 4];   //a = 1, b = 2, c = 3
let [a, [b], c] = [1, [2, 3], 4]; //a = 1, b = 2, c = 4
let [a, [, b], c] = [1, [2, 3], 4]; //a = 1, b = 3, c = 4

如果左边的形式多于右边,那么没有匹配上的值等于undefined

1
let [x, y] = [1];   //x = 1, y = undefined

对象的解构赋值

对象的解构赋值实现的规则是变量与属性同名,而非顺序,其实这一点也好理解,因为数组本身也是一种对象,它的key就是索引(即顺序),它的value是索引对应的值,在数组的解构赋值中,等号两边对应的顺序相同即他们的key相同,所以看起来是根据顺序赋值的,实际上还是根据属性赋值的。

1
let {bar, foo} = {foo: "aaa", bar: "bbb"};  //foo = "aaa", bar = "bbb"

如果在右边的值中没有对应的变量名,则取不到值,变量等于undefined

1
let {baz} = {foo: "aaa", bar: "bbb"};   //baz = undefined

如果变量名与属性名不同,想要取属性的值,需要将属性的值先赋给变量

1
let {foo: baz} = {foo: "aaa", bar: "bbb"};  //baz = "aaa"

解构赋值也可以应用于嵌套结构的对象:

1
2
3
4
5
6
7
let obj = {
p: [
'hello',
{y: 'world'}
]
}
let {p: [x, {y}]} = obj; // x = "hello" y = "world"

默认值

数组和对象的解构赋值都可以接受默认值,即在等号左边的形式中给变量赋值,如果右边对应变量有新的值,则变量等于新的值,如果对应变量没有新的值,则变量等于默认值。

1
2
3
4
//数组的默认值
let [x, y = "b"] = ["a"]; //x = "a", y = "b"
//对象的默认值
let {x, y = 5} = {x:1}; // x = 1, y = 5

默认值生效的条件是,**对应变量的值严格等于undefined**,否则默认值无法生效。

1
2
3
let [x = 1] = [];          // x = 1
let [x = 1] = [undefined]; // x = 1
let [x = 1] = [null]; // x = null

因为null不严格等于undefined,所以默认值不会生效。

对象的默认值设置也是一样:

1
2
let {x = 3} = {x: undefined};   // x = 3
let {x = 3} = {x: null}; // x = null

需要注意的是,对象的默认值设置也还是用等号赋值,而不是let {x: 3} = {x: undefined}


对象、数组混合赋值

上面提到数组是一种特别的对象,它的key就是索引,所以可以利用这一点对数组进行对象属性的解构。

1
2
3
let arr = [1, 2, 3];
let {0: first, [arr.length - 1]: last} = arr;
console.log(first, last); //1, 3

字符串的解构赋值

字符串也可以解构赋值,因为字符串是一个类似数组的对象,具有原生的Iterator接口。

1
2
3
4
5
6
7
8
9
let str = "hello";
let iter = str[Symbol.iterator]();

console.log(iter.next()); //{ value: 'h', done: false }
console.log(iter.next()); //{ value: 'e', done: false }
console.log(iter.next()); //{ value: 'l', done: false }
console.log(iter.next()); //{ value: 'l', done: false }
console.log(iter.next()); //{ value: 'o', done: false }
console.log(iter.next()); //{ value: undefined, done: true }

字符串的解构赋值有两个用法:

  1. 将其转换成一个类似数组的对象

    1
    const [a, b, c, d, e] = "hello"; //a = "h", b = "e", c = "l", d = "l", e = "o"
  2. 利用length属性赋值

    1
    2
    let [length: len] = "hello";
    console.log(len) //5 len即"hello".length

数值和布尔值的解构赋值

解构赋值是,如果等号右边是数值和布尔值,会先转为对象。

1
2
3
4
5
let {toString: s} = 123;
s === Number.prototype.toString; //true

let {toString: s} = true;
s === Boolean.prototype.toString; //true

如果等号右边无法转为对象,就会报错。

1
2
let {prop: x} = undefined;  //TypeError
let {prop: y} = null; //TypeError

函数的解构赋值

函数的参数也可以解构赋值。

1
2
3
function add([x, y]) {
return x + y;
}

以上函数的参数是数组,但传入函数的那一刻,就解构成x和y,所以可以直接使用x和y的值。

函数的参数也可以设定默认值。

1
[1, undefined, 3].map((x='yes') => x); //[1, 'yes', 3]

具体用途

  1. 用于交换变量的值

    1
    2
    3
    let x = 1;
    let y = 2;
    [x, y] = [y, x]; //x = 2, y = 1
  2. 函数返回多个值

    通常函数只能返回一个值,如果需要返回多个值,可以将返回的值放在一个数组或者对象里,通过解构赋值获取

    1
    2
    3
    4
    function example() {
    return [1, 2, 3];
    }
    let [a, b, c] = example();
  3. 提取JSON数据

    1
    2
    3
    4
    5
    6
    7
    let json = {
    id: 42,
    status: "ok",
    data: [867, 5309]
    };
    let {id, status, data: number} = json;
    console.log(id, status, number); //42, "ok", [867, 5309]
  4. 遍历Map结构

    Map原生支持Iterator接口,可以利用for...of循环遍历,配合解构赋值,获取key和value值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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

    需要注意的是,虽然Map看起来和Object一样都是键值对的形式,但是Object对象是不支持Iterator接口的,所以无法用for...of循环来获取Object对象的key和value

  5. 输入模块的指定方法

    加载模块时,需要指定输入哪些方法,解构赋值能够使得输入语句非常清晰。

    1
    const {log, sin, cos} = require("Math");

ES6学习笔记的内容主要基于阮一峰老师的《ECMAScript 6入门》,原文请见链接