ES6-解构赋值
in JavaScript with 0 comment

ES6-解构赋值

in JavaScript with 0 comment

数组解构赋值

在ES5中我们同时为多个变量赋值时,需要一个一个分开进行,如var a = 1, b = 2。而ES6则为我们提供了一种更简洁的方式var [a, b] = [1, 2],这就是数组的解构赋值。

基础语法

以数组的形式,对多个变量进行对应的赋值:

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

而且数组内还可以嵌套数组,只要两边的模式相同,就可以赋值成功:

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

但是如果模式不同,则会解构失败,抛出异常:

try {
    let [a, [[b, c]]] = [1, [2, 3]]; // TypeError: [1,[2,3]] is not iterable
} catch (e) {
    console.log(e)
}

如果左边的变量比右边多,多余的变量赋值为undefined

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

但是反过来是可以赋值成功的,这种情况称为不完全解构:

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

也可以跳过中间的部分值:

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

还可以利用剩余操作符( ... )一次性解构出所有剩余所有的值:

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

默认值

数组解构可以提供默认值:

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

只有严格等于undefined时默认值才会生效:

let [a, b, c, d = 2] = [1, 2, 3, null];
console.log(a, b , c, d); // 1 2 3 null 此时默认值没有生效

原理

数组的解构赋值实际上是利用了ES6新增的 迭代器(Iterator) 来实现的,所以只要实现了Iterator接口的都可以用数组解构赋值。

所以string类型可以解构赋值:

let [a, b, c] = 'abc';
console.log(a, b, c); // a b c

Set类型可以解构赋值:

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

Map类型也可以解构赋值:

let map = new Map([['a', 1], ['b', 1], ['c', 1]]);
let [a, b, c] = map;
console.log(a, b, c); // [ 'a', 1 ] [ 'b', 1 ] [ 'c', 1 ]

实现了Iterator接口的对象也是可以的:

let iterable = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
let [a, b, c] = iterable;
console.log(a, b, c); // a b c

其它未实现Iterator接口的数据类型和对象则不能使用数组的解构赋值:

// 下面的表达式全部会抛出TypeError的异常
let [a, b, c] = null;
let [a, b, c] = undefined;
let [a, b, c] = 123;
let [a, b, c] = {};
let [a, b, c] = true;

应用

数组的解构赋值的应用场景有很多,下面列举一些典型的应用场景:

交换变量

可以利用解构赋值来交换两个变量的值:

let [x, y] = [1, 2];
console.log(x, y); // 1 2
[x, y] = [y, x];
console.log(x, y); // 2 1

这种交换写法很简捷,语义也很清晰。而且还可以用来交换多个变量的值:

let [x, y, z] = [1, 2, 3];
console.log(x, y, z); // 1 2 3
[x, y, z] = [z, x, y];
console.log(x, y, z); // 3 1 2

从函数返回多个值

有时我们需要从一个函数同时返回多个值,这时我们就可以利用数组的解构赋值来完成。

例如我们需要找出一个整数数组中的最大值和它的索引,代码可以这样写:

function findMax(nums) {
    let [index, max] = [0, nums[0]];
    for (let i = 0; i < nums.length; i ++) {
        if (nums[i] > max) {
            [index, max] = [i, nums[i]]
        }
    }
    return [index, max];
}

调用的结果如下:

let [index, max] = findMax([1, 3, 5, 0, 11, 3, 5]);
console.log(index, max); // 4 11

这样就很方便地完成了多个值的同时返回。

遍历Map

遍历Map时,我们可以利用数组的解构赋值同时取出Mapentries中的keyvalue

let map = new Map([
    ['key1', 'value1'],
    ['key2', 'value2'],
]);
for (let [key, value] of map) {
    // key1 value1
    // key2 value2
    console.log(key, value); 
}

对象解构赋值

解构赋值除了数组外,也是可以用在对象上的。

基础语法

数组的解构赋值是按遍历的顺序来赋值的,赋值的结果由元素的位置来决定;但对象的属性是没有顺序的,所以它的赋值是由属性名称来决定的。

下面例子中的b虽然在位置上与对象中的属性b并不对应,但依然赋值为了2,而c由于对象中没有同名的属性,所以未被赋值:

let {b, c} = {a: 1, b: 2};
console.log(b, c); // 2 undefined

变量的解构赋值也可以与数组嵌套使用:

let {a, b: [{c}]} = {a: 'a', b: [{c: 'c'}]};
console.log(a, c); // a c

默认值

对象的解构赋值也可以提供默认值:

let {a, b='default'} = {a: 'a'};
console.log(a, b); // a default

同样只有严格等于undefined时默认值才会生效:

let {a, b='default'} = {a: 'a', b: null};
console.log(a, b); // a null 默认值没有生效

原理

先看下面这个例子:

let {a:aa, b} = {a: 'a', b: 'b'};
console.log(aa); // a
console.log(a); // ReferenceError: a is not defined

这个例子中被赋值的是aa而不是a,从中我们可以看出变量的解构赋值是先找到同名属性,然后再赋给对应的变量。实际上被赋值的是后者而不是前者。

常用形式let {a, b} = {a: 'a', b: 'b'};实际上是let {a: a, b: b} = {a: 'a', b: 'b'};简写形式。

应用

变量的解构赋值应用场景也很广泛。

函数传参

定义函数时,如果使用对象的解构赋值来接收参数,可以有几个明显的优势。

  1. 可以使用命名参数:
var es5Function = function(param1, param2) {
    console.log(param1, param2);
};

const es6Function = function({param1, param2}) {
    console.log(param1, param2);
};

// true false
// 调用时无法很清晰地了解传递的是什么参数
es5Function(true, false);
// true false
// 调用时需要指定参数名,参数的作用就一目了然了
es6Function({param1: true, param2: false});
  1. 可以忽略参数顺序
var es5Function = function(param1, param2) {
    console.log(param1, param2);
};

const es6Function = function({param1, param2}) {
    console.log(param1, param2);
};

// param1 param2
// 必须要按照指定的顺序传递参数
es5Function('param1', 'param2');
// param1 param2
// 可以忽略参数的顺序,指定参数名即可
es6Function({param2: 'param2', param1: 'param1'});
  1. 可以指定默认值
// es5的函数,要赋默认值需要进行特殊处理
var es5Function = function(param1, param2) {
    param1 = param1 || 'default1';
    param2 = param2 || 'default2';
    console.log(param1, param2);
};

// 解构赋值传参可以直接赋默认值
// ={} 是利用了es6函数本身可以赋默认值的特性,这样我们可以不传参数调用这个函数
const es6Function = function({param1 = 'default1', param2 = 'default2'} = {}) {
    console.log(param1, param2);
};

es5Function(); // default1 default2
es6Function(); // default1 default2
es6Function({}); // 如果函数定义时没有 ={},就需要这样调用

函数返回多个值

之前我们利用了数组的解构赋值来从函数中同时返回多个值,实际上这个过程也是可以用对象的解构赋值来完成的:

function findMax(nums) {
    let {index, max} = {index: 0, max: nums[0]};
    for (let i = 0; i < nums.length; i ++) {
        if (nums[i] > max) {
            // 对象的解构也可以无声明赋值,但外面必须加上括号
            // 否则左边的{index, max}会被认为是块语法而不是对象字面量,这样的语法是无效的
            ({index, max} = {index: i, max: nums[i]});
        }
    }
    return {index, max};
}

调用:

let {index, max} = findMax([1, 3, 5, 0, 11, 3, 5]);
console.log(index, max); // 4 11

引入模块中的指定方法

例如按需引入UI库中的部分组件:

import {AtList, AtListItem, AtMessage} from "taro-ui";