Skip to main content

解构赋值

具有 Iterator 接口的数据解构

所有具有 Iterator 接口的数据结构都可以使用类似数组的结构赋值语法。

常见的实现了 Iterator 结构的数据结构有:

  • 数组
  • 字符串
  • Set
  • Map
  • Generator 函数

数组的解构

解构过程中,会将数组的每个元素,按顺序依次赋值给解构变量。

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

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 = []

如果结构到 undefined 类型的元素,我们可以给它使用默认值。

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

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

如果默认值是一个表达式,那么只有当默认值生效时才会执行这个表达式。

function f() {
console.log('aaa');
}

let [x = f()] = [1];

上面的 f() 函数是不会被执行的,因为默认值并没有生效。

你可以使用下面的方式,快速交换两个变量的值。

let a = 10, b = -10;

// 左边是数组解构赋值的形式,右边是一个数组
[a, b] = [b, a];
// a = -10
// b = 10

字符串的解构

字符串是一个类数组对象。

const [a, b, c, d, e] = 'hello';
// a = "h"
// b = "e"
// c = "l"
// d = "l"
// e = "o"

我们还可以通过对象解构的方式,解构出字符串的 length 属性。

const { length } = 'hello';
// length = 5

Set 的解构

let [x, y, z] = new Set(['a', 'b', 'c']);
// x = "a"

Map 的解构

可以传入一个二维数组,里面的子数组是一个二元组,第一个元素表示 key,第二个元素表示 value,解构出来的变量也是二元组。

let [a, b] = new Map([
["name", "张三"],
["age", 18],
]);

// a = ["name", "张三"]
// b = ["age", 18]

Generator 函数的解构

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();
// sixth = 5

对象的解构

基本使用

对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量

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

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

解构变量的别名

给解构变量取别名。

var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
// baz = "aaa"

上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo

这样一来无法再获取到 foo 变量。

先声明再解构赋值

不一定必须在变量定义的时候进行解构,你也可以先声明再通过解构来给变量赋值。

let foo;
({foo} = {foo: 1});
// foo = 1

注意一定要加上圆括号,否则语法报错。

解构嵌套结构的对象

和数组一样,解构也可以用于嵌套结构的对象。

let obj = {
p: [
'Hello',
{ y: 'World' }
]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

使用 pobj 的属性 p 解构,此时 p 是一个数组,再通过 p: [a, b] 来解构这个数组,a 表示数组的第一个元素,等价于上面的 xb 表示数组的第二个元素,等价于上面的 { y },这里使用 { y } 再给 b 对象进行解构。

解构变量默认值

机制和数组一样,使用 === 来判断解构数据的值是否为 undefined,如果是则让默认值生效。

let {x = 3} = {x: undefined};
// x = 3

let {y = 3} = {y: null};
// y = null

对象解构的方式解构数组

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

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

其他数据类型的解构

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

函数参数的解构赋值

如果传入的函数参数是一个数组类型的数据,那么在函数形参列表中就可以使用数组的解构赋值。

function add([x = 0, y = 0]){
return x + y;
}

add([1, 2]); // 3
add([]); // 0
add(); // 会报错,因为无法给 undefined 进行数组的解构
function add([x = 0, y = 0] = []){
return x + y;
}

add(); // 0

这里是使用了函数参数的默认值,调用函数时如果没有传入对应参数,则会取默认值。

function add([...nums]){
return nums.reduce((pre, cur) => pre + cur, 0);
}

add([1, 2, 3, 4, 5]); // 15

如果传入的函数参数是一个对象类型的数据,那么在函数形参列表中就可以使用对象的解构赋值。

function move({ x, y } = { x: 0, y: 0 }) {
return [x, y];
}

请注意上面代码和下面的区别。

function move({ x = 0, y = 0 }) {
return [x, y];
}

前者使用了函数参数的默认值,后者则没有。

rest 运算符

基本使用

在一个 可迭代对象 或者 普通对象 前使用 ... 则可以 展开其内容

var arr1 = [ 1, 2, 3, 4 ];
var arr2 = [ 5, 6, 7 ];
var arr3 = [ ...arr1, ...arr2 ]; // 将 arr1 和 arr2 中的元素提取出来后放入一个新的数组,给 arr3 赋值
var arr4 = [..."hello"]; // ["h", "e", "l", "l", "o"]

var obj1 = { name: 'kl', age: 18 };
var obj2 = { name: 'momo', age: 20 };
// 将 obj1 和 obj2 中的属性提取出来放入新的对象中
var couple = {
husband: { ...obj1 },
wife: { ...obj2 }
};

也可以使用 ... 给一个变量进行赋值

// 右边数组的前两个元素赋值给了 a 和 b 变量,后面剩余的元素聚合为一个子数组赋值给 c 变量
var [ a, b, ...c ] = [ 1, 2, 3, 4, 5, 6, 7 ];

// 将左边对象的 name 属性值赋值给右边的 name 变量,左边对象剩余的其他属性值聚合为一个新的对象赋值给 other 变量
var { name, ...other } = { name: 'kl', age: 18, gender: 1, num: 1001 };

函数参数上的使用

将数组展开作为函数实参。

// 在调用函数时传入实参的扩展作用
function fn(a, b, c) {}
fn(...[1, 2, 3]); // 将数组中的元素依次扩展给 a b c 赋值

在函数形参上将所有传来的参数聚合到一个数组中。

// 在函数的形参上的聚合作用
function fn(...arr) {} // 传入的参数会保存在 arr 数组中
fn(1, 2, 3);