变量的解构赋值

127 阅读5分钟

数组的解构赋值

基本用法

数组的元素是按顺序排列的,变量的取值是由位置决定的。如果解构不成功,变量的值就等于 undefined。

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [, , third] = ["foo", "bar", "baz"];
third // "baz"

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ["a"];
x // "a"
y // undefined
z // []

// 以下两种情况 foo 都等于 undefined
let [foo] = [];
let [bar, foo] = [1];

不完全解构,是指等号左边的模式只匹配到一部分的等号右边的数组,但是解构可以成功。如下:

let [x, y] = [1, 2, 3];
x // 1
y // 2

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

默认值

解构时允许指定默认值,当赋值数组对应的值严格等于(===) undefined 时,使用默认值进行赋值。默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x = 'a', y = 'b'
let [x, y = 'b'] = ['a', undefined]; // x = 'a', y = 'b'

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

对象的解构赋值

对象的属性没有排序,变量必须与属性同名才能取到正确的值。如果变量名与属性名不一致,必须采用"属性名:变量名"的写法。实际上,对象的解构赋值是先找到前者的同名属性,然后再赋值给后者对应的变量。结构赋值不会赋值继承自原型对象上的属性

let { bar, baz, foo } = { foo: "aaa", bar: "bbb" }; // 相当于下一句
let { bar: bar, baz: baz, foo: foo } = { foo: "aaa", bar: "bbb" };
bar // "bbb"
baz // undefined
foo // "aaa"

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

let { foo: baz } = { foo: "aaa", baz: "bbb" };
baz // "aaa"
foo // 报错,foo 未定义

如果解构失败,变量的值等于 undefined。对象解构时也可以指定默认值,默认值生效的条件是对象的属性值严格等于 undefined。

let { x: y = 3 } = { x: 5 };
y // 5

如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,将会报错

let { foo: { bar } } = { baz: "baz" } ;

将一个已经声明的变量用于解构赋值时,很容易出错。js 引擎会将下列的{x} 理解成代码块,从而发生语法错误,只有不将大括号放在行首,避免 js 引擎将其解释为代码块,才可以解决这个问题

1et x;
{x} = {x:1}; // 报错SyntaxError
({x} = {x:1}); // 成功

由于数组本身是特殊的对象,因此可以对数组进行对象属性结构

let arr = [1, 2, 3];
let { 0 : first, [arr.length - 1] : last } = arr;
first // 1
last // 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

数值和布尔值的解构赋值

解构赋值时,只要等号右边不是对象或数组,就会先将其转换为对象。undefined 和 null 无法转换为对象,因此解构时会报错。

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

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

let { prop:x } = undefined; // TypeError
let { prop:z } = null; // TypeError

函数参数的解构赋值

函数参数的解构赋值可以使用默认值,undefined 就会触发函数参数的默认值

function move1({ x = 0, y = 0 } = {}) {
    return[x, y];
}
move1({ x: 3, y: 8 }); // [3, 8]
move1({ x: 3 }); // [3, 0]
move1({}); // [0, 0]
move1(); // [0, 0]

function move2({ x, y } = { x: 0, y: 0 }){
    return [x, y];
}
move2({ x: 3, y: 8 }); // [3, 8]
move2({ x: 3 }); // [3, undefined]
move2({}); // [undefined, undefined]
move2(); // [0, 0]

圆括号问题

不能使用圆括号的情况

  1. 变量声明语句

    // 以下语句全部报错,变量声明语句,模式不能使用圆括号
    let [(a)] = [1];
    
    let { x: (c) } = {};
    let ({ x: c }) = {};
    let { (x: c) } = {};
    let { (x): c } = {};
    
  2. 函数参数

    函数参数也属于变量声明语句,因此也不能使用圆括号

    // 以下函数参数声明都报错
    function f([(z)]) {
        return z;
    }
    
    function f([z, (x)]) {
        return x;
    }
    
  3. 赋值语句的模式

    ({ p: a }) = { p: 42 };
    ([a]) = [5];
    // 这两行赋值语句把整个模式放在圆括号中,导致报错
    
    // 将一部分模式放在圆括号之中,导致报错
    [({ p: a }), { x: c }] = [{}, {}];
    

可以使用圆括号的情况

可以使用圆括号只有一种情况:赋值语句 (注意和声明语句的区别) 的非模式部分可以使用圆括号。如下:

[(b)] = [3];
({ p: (d) } = {});
[(parseInt.prop)] = [3];

变量解构赋值的用途

  1. 交换变量的值

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

    function example1() {
        return [1, 2, 3];
    }
    let [a, b, c] = example1();
    
    function example2() {
        return {
            foo: 1,
            bar: 2
        }
    }
    let { foo, bar } = example2();
    
  3. 函数参数的定义

    function f1([x, y, z]) {}
    f1([1, 2, 3]);
    
    function f2({x, y, z}) {}
    f2({ y: 2, x: 1, z: 3 });
    
  4. 提取 JSON 数据

    let json = {
        id: 42,
        name: "chen",
        year: 22,
        money: [666, 888]
    }
    let { id, name, year: age, money } = json;
    
  5. 函数参数的默认值

  6. 遍历 Map 结构

    Map 结构原生支持 Iterator 接口,配合变量的解构赋值获取键名和键值非常方便

    var map = new Map();
    map.set('first', 'hello');
    map.set('second', 'world');
    
    // 获取键名和键值
    for(let [key, value] of map) {
        console.log(key + " is " + value);
    }
    // 获取键名
    for(let [key] of map) {}
    // 获取键值
    for(let [, value] of map) {}
    
  7. 输入模块的指定方法

    const { SourceMaoConsumer, SourceNode } = require("source-map");