解构赋值
具有 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"
使用 p
给 obj
的属性 p
解构,此时 p
是一个数组,再通过 p: [a, b]
来解构这个数组,a
表示数组的第一个元素,等价于上面的 x
,b
表示数组的第二个元素,等价于上面的 { 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);