作者:米书林
参考文章:《菜鸟教程》、《 ECMAScript 6 入门》(阮一峰)
解构的定义
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
结构表达式的组成
1.解构的源:解构表达式值的来源,表达式的右边部分
2.解构的目标:定义解构表达式值的存储变量,表达式的左边部分
数组的解构
基本用法
let [a,b,c] = [1,2,3];
console.log(a); \\ 1
console.log(b); \\ 2
console.log(c); \\ 3
只要表达式左右的结构一样,左边对应的变量能在右边找到对应的值,就能将右边的值赋值给左边的变量。
我们可以用解构赋值来简化后台给我们传递的数据,减少数据遍历的循环次数
可嵌套
let [a, [[b], c]] = [1, [[2], 3]];
// a = 1
// b = 2
// c = 3
若上述变量b不用[]括起来,则b返回的值带有中括号,如下:
let [a, [b, c]] = [1, [[2], 3]];
// a = 1
// b = [2]
// c = 3
可忽略
let [a, , b] = [1, 2, 3];
// a = 1
// b = 3
若被忽略项在末尾,直接忽略;若被忽略项不在末尾,则需要用","来填补,若省略",",则浏览器默认会在末尾补齐
例如:
let [a, , b] = [1, 2, 3, 4];
// a = 1
// b = 3
let [a, , , b] = [1, 2, 3, 4];
// a = 1
// b = 4
不完全解构
let [a = 1, b] = [];
// a = 1
// b = undefined
若定义的变量在右侧无对应项,则会被赋值为:undefined
;
若定义的变量给定默认值,且匹配结果为undefined
,则解构的结果为默认值。
剩余运算符
let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]
注意:含有剩余运算符项表达式要放在最后一项
结构源为字符串
let [a,b,c] = "ES6"
// a = "E"
// b = "S"
// c = "6"
解构默认值
当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。
let [a = 3, b = a] = [undefined]; // a = 3, b = 3
let [a = 3, b = a] = [1]; // a = 1, b = 1
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
分析:
第一个表达式a匹配的结果为undefined
,故返回a的默认值,a的默认值为3,b匹配的结果为undefined
,故返回b的默认值,b的默认值为b=a,a此时为3,故b为3;
第二个表达式a匹配的结果为1,故返回a的匹配值1,b匹配的结果为undefined
,故返回b的默认值,b的默认值为b=a,a此时为1,故b为1;
第三个表达式a匹配的结果为1,故返回a的匹配值1,b匹配的结果为2,故返回b的匹配值2;
解构匹配值为null
let [a=12] = [null]
// a = null
解构Set结构
let [a, c, c] = new Set(['a', 'b', 'c']);
// a = "a"
// b = "b"
// c = "c"
不只是数组,只要能遍历的结构都能使用解构
不能遍历的结构不能被解构,例如:
let [a] = 1;
let [a] = false;
let [a] = NaN;
let [a] = undefined;
let [a] = null;
let [a] = {};
以上代码浏览器执行后都会报错。
对象模型的解构
基本
let {name,age} = {name:"张三",age:20}
// name = "张三"
// age = 20
注意:此处等号左侧的变量name是name:name缩写,age是age:age的缩写,ES6新特性规定对象的属性和值用同一变量可以简写为一个。因此:结构后的结果实际上是赋值给了对象属性值的变量
对象属性值的解构
let {name:name1} = {name:"李四"}
// name = undefined(浏览器报错:name is not defined)
// name1 = "李四"
上面代码可以看出解构的结果赋值给了对象属性值对应的变量
可嵌套
let {p: [x, { y }] } = {p: ['hello', {y: 'world'}] };
// x = 'hello'
// y = 'world'
可忽略
let {p: [x, { }] } = {p: ['hello', {y: 'world'}] };
// x = 'hello'
let {p: [, { y }] } = {p: ['hello', {y: 'world'}] };
// y = 'world'
不完全解构
let {p: [{ y }, x ] } ={p: [{y: 'world'}] };
// x = undefined
// y = 'world'
剩余运算符
let {a, b, ...rest } = {a: "张三", b: "李四", c: "王五", d: "李二"};
// a = "张三"
// b = "李四"
// rest = {c: "王五", d: "李二"}
解构默认值
let {a = 1, b = 2} = {a: 3};
// a = 3; b = 2;
let {a: aa = 10, b: bb = 5} = {a: 3};
// aa = 3; bb = 5;
默认解构值在匹配结果为undefined
时触发。
已经声明的变量用于解构赋值
// 错误的写法
let x;
{x} = {x: 1};
// Uncaught SyntaxError: Unexpected token '='
以上代码浏览器会报错,原因是浏览器将大括号解析成了代码块,两个代码块之间要么为空格,要么为“;”,故此处报错。解决的办法是用()将它转换成一个表达式
let x;
({x} = {x: 1});
应用场景
1.任意个参数求和
传统方法:
js传统方法需要借助arguments来实现,具体方法如下:
function sum(){
var sum = 0;
var len = arguments.length; // 将长度存储在变量中可以减少循环次数,提升性能
for(var i = 0;i < len;i++){
sum +=arguments[i]
}
return sum;
}
sum(1,2,3); // 6
sum(1,2,"4"); // "34"
此处只是为了对比传统方法和ES6新方法求和,未作数值类型转化,故第二个调用出现了字符串拼接
ES6新方法:
ES6用剩余项表达式和解构来存储参数个数,求和的方法如下:
function sum(...nums){
let sum = nums.reduce((x,y)=>{return x+y})
return sum
}
sum(1,2,3); // 6
sum(1,2,"4"); // "34"
注意此处使用了ES6数组的新方法:reduce,reduce的作用是汇总,输入一堆,输出一个结果
在这里我们可能感觉两种方法的实现原理好像是一样的额,其实并不然,传统方法借助的arguments是一个类数组,虽然我们可以像数组一样用下标,即arguments[0],arguments[1]
,...的方式去访问它,也能通过arguments.length
来计算传入参数的个数,还能使用arguments.callee
(注意在ES5中这个属性已被废弃,caller
不是的属性),然而它还是没有数组常用的一些方法,比如reduce,pop,push,shift,unshift
等,所以用传统方法求参数和要比ES6的代码多好几行。
2.不借助第三个参数交换变量的值
传统方法:
传统方法(ES6之前,C语言等)要交换两个变量的值需要借助第三个变量,在js中的实现方法大概如下:
let a = 1;
let b = 2;
let c = a;
console.log(a,b,c);
a = b;
b = c;
// a = 2;
// b = 1;
// c = 1;
所以,传统方法大概需要5行代码才能实现。
ES6方法:
ES6中我们不需要借助第三个参数,具体实现方法如下:
let a = 1;
let b = 2;
[a, b] = [b, a];
// a = 2;
// b = 1;
从上面可以看出我们只需要三行代码就能完成参数的交换。
3.收集函数的剩余参数,用作公共函数可选项
我们都知道js中传递的实参可以不和形参一一对应;
形参个数大于实参个数时,多余的形参会被初始化为undefined
(函数声明整体提升,随后找形参并将形参初始化为undefined,最后才是实参和形参相统一,所以没有实参对应的形参值仍然是undefined);
当形参个数小于实参个数时,多余的实参会被忽略;
因此,一些场景下即使我们只用到一次的或者可能不会用到的参数都要在定义形参来传递,一些参数个数不确定的场景下我们只能通过多定义形参来解决问题,但是这显然有些笨拙。
在ES6新特性中,我们可以通过“...+变量名”来解决参数传递的问题。
下面我们可以通过函数来简单模范api的可选参数,我们把必填参数放在前面,把可选参数放在剩余项里,这样我们的代码如下:
function dateFormat(a,..b){
...
}
上面代码a是必填参数,b是可选参数的集合,这样我们就能通过一个变量将不常用的参数放到一个集合中了。
4.返回多个函数结果
ES6之前我们想从函数返回多个结果,只能将它们放在数组或对象里返回,想要获取每一个返回结果,少不了循环和遍历语句,这很让人头疼。
有了ES6后我们就可以将函数执行的结果一个一个的提取出来。大概的代码如下:
// 返回一个数组
function test() {
return [1, 2, 3];
}
let [a, b, c] = test();
// 返回一个对象
function test() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = test();
5.简化底层数据的调用
很多时候后台返回给我们的都是一些对象和数组的混合嵌套体,对于深层数据的访问我们往往需要写一长串“.”引用,用解构赋值我们就可以将这些数据单独获取出来。
6.设置函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo';
这样的语句。
7.遍历 Map 结构
任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
如果只想获取键名,或者只想获取键值,可以写成下面这样。
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
8.输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");