解构赋值
具有 Iterator 接口的数据解构
所有具有 Iterator 接口的数据结构都可以使用类似数组的结构赋值语法。
常见的实现了 Iterator 结构的数据结构有:
- 数组
 - 字符串
 SetMapGenerator函数
数组的解构
解构过程中,会将数组的每个元素,按顺序依次赋值给解构变量。
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);