1、基本语法
1.1、数组
// 基础类型解构
let [a, b, c] = [1, 2, 3]
console.log(a, b, c) // 1, 2, 3
// 对象数组解构
let [a, b, c] = [{name: '1'}, {name: '2'}, {name: '3'}]
console.log(a, b, c) // {name: '1'}, {name: '2'}, {name: '3'}
// ...解构
let [head, ...tail] = [1, 2, 3, 4]
console.log(head, tail) // 1, [2, 3, 4]
// 嵌套解构
let [a, [b], d] = [1, [2, 3], 4]
console.log(a, b, d) // 1, 2, 4
// 解构不成功为undefined
let [a, b, c] = [1]
console.log(a, b, c) // 1, undefined, undefined
// 解构默认赋值
let [x = 1] = [undefined];// x=1;
let [x = 1] = [null];// x=null; // 数组成员严格等于undefined,默认值才会生效
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: y is not defined 因为x用y做默认值时,y还没有声明
1.2 对象
// 对象属性解构
let { f1, f2 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2
// 可以不按照顺序,这是数组解构和对象解构的区别之一
let { f2, f1 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2
// 解构对象重命名
let { f1: rename, f2 } = { f1: 'test1', f2: 'test2' }
console.log(rename, f2) // test1, test2
// 嵌套解构
let { f1: {f11}} = { f1: { f11: 'test11', f12: 'test12' } }
console.log(f11) // test11
// 默认值
let { f1 = 'test1', f2: rename = 'test2' } = { f1: 'current1', f2: 'current2'}
console.log(f1, rename) // current1, current2
1.3 字符串/数值/布尔值
// String
let [ a, b, c, ...rest ] = 'test123'
console.log(a, b, c, rest) // t, e, s, [ 't', '1', '2', '3' ]
let {length : len} = 'hello'; // en // 5
// number
let {toString: s} = 123;
s === Number.prototype.toString // true
// boolean
let {toString: s} = true;
s === Boolean.prototype.toString // true
// Map
let [a, b] = new Map().set('f1', 'test1').set('f2', 'test2')
console.log(a, b) // [ 'f1', 'test1' ], [ 'f2', 'test2' ]
// Set
let [a, b] = new Set([1, 2, 3])
console.log(a, b) // 1, 2
2、使用场景
2.1、 浅拷贝
let colors = [ "red", "green", "blue" ];
let [ ...clonedColors ] = colors;
console.log(clonedColors); // "[red,green,blue]"
注意这里是浅拷贝
2.2、 交换变量
let x = 1;
let y = 2;
[x, y] = [y, x];
2.3、遍历Map结构
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
2.4、函数参数
function add(a,b,{c,d}){
// ...
}
add(1,2,{});
add(1,2)//Uncaught TypeError: Cannot destructure property `c` of 'undefined' or 'null'
解构赋值的规则是,若等号右边的值不是对象或者数组,就会先将其转化成对象。由于undefined和null无法转化成对象,所以对其进行解构赋值时会报错。
它实际上是这样运行的:
function add(a, b, options) {
let { c,d } = options;
// ...
}
由于add(1,2)
没有传options
导致异常,如果我们options是选填的那么可以像下面这样
function add(a,b,{c,d}={}){
// ...
}
当然大部分情况我们可以给默认值
function move ({x:0,y:0}={}){}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
function move ({x,y}={x:0,y:0}){}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
上面代码是为函数move
的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。 undefined
就会触发函数参数的默认值。
[1, undefined, 3].map((x = 'yes') => x);
3、基本原理
解构是ES6
提供的语法糖,其实内在是针对可迭代对象的Iterator
接口,通过遍历器按顺序获取对应的值进行赋值。这里需要提前懂得ES6
的两个概念: - Iterator - 可迭代对象
3.1、Iterator概念
Iterato
r是一种接口,为各种不一样的数据解构提供统一的访问机制。任何数据解构只要有Iterator
接口,就能通过遍历操作,依次按顺序处理数据结构内所有成员。ES6中的for of的语法相当于遍历器,会在遍历数据结构时,自动寻找Iterator
接口。 Iterator作用: - 为各种数据解构提供统一的访问接口 - 使得数据解构能按次序排列处理 - 可以使用ES6最新命令 for of进行遍历
function makeIterator(array) {
var nextIndex = 0
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++]} :
{done: true}
}
};
}
var it = makeIterator([0, 1, 2])
console.log(it.next().value) // 0
console.log(it.next().value) // 1
console.log(it.next().value) // 2
3.2、可迭代对象
可迭代对象是Iterator
接口的实现。这是ECMAScript 2015
的补充,它不是内置或语法,而仅仅是协议。任何遵循该协议点对象都能成为可迭代对象。可迭代对象得有两个协议:可迭代协议和迭代器协议。
可迭代协议
:对象必须实现@@iterator
方法。即对象或其原型链上必须有一个名叫Symbol.iterator
的属性。该属性的值为无参函数,函数返回迭代器协议。
迭代器协议
:定义了标准的方式来产生一个有限或无限序列值。其要求必须实现一个next()
方法,该方法返回对象有done(boolean)
和value
属性。
通过以上可知,自定义数据结构,只要拥有Iterator
接口,并将其部署到自己的Symbol.iterator
属性上,就可以成为可迭代对象,能被for of
循环遍历。
3.3、解构语法糖
String
、Array
、Map
、Set
等原生数据结构都是可迭代对象,可以通过for of
循环遍历它。故可以通过ES6解构语法糖依次获取对应的值。
// String
let str = 'test'
let iterFun = str[Symbol.iterator]
let iterator = str[Symbol.iterator]()
let first = iterator.next() // 等效于 let [first] = 'test'
console.log(iterFun, iterator, first)
// 打印
// [Function: [Symbol.iterator]], {}, { value: 't', done: false }
// Array
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
// 以下等效于 let [first, second, third, four] = ['a', 'b', 'c']
let first = iter.next() // { value: 'a', done: false }
let second = iter.next() // { value: 'b', done: false }
let third = iter.next() // { value: 'c', done: false }
let four = iter.next() // { value: undefined, done: true }
4、性能
当我们有解构赋值的形式来做函数参数时,执行的时候会增加很多中间变量,内存也会比之前高。但是业务代码还是更加关注可读性和可维护性。如果你写的是库代码,可以尝试这种优化,把参数展开后直接传递,到底能带来多少性能收益还得看最终的基准测试。