咱们常常定义许多对象和数组,而后有组织地从中提取相关的信息片断。在ES6中添加了能够简化这种任务的新特性:解构。解构是一种打破数据结构,将其拆分为更小部分的过程。本文将详细介绍ES6解构赋值node
在ES5中,开发者们为了从对象和数组中获取特定数据并赋值给变量,编写了许多看起来同质化的代码算法
let options = { repeat: true, save: false }; // 从对象中提取数据 let repeat = options.repeat, save = options.save;
这段代码从options对象中提取repeat和save的值,并将其存储为同名局部变量,提取的过程极为类似数组
若是要提取更多变量,则必须依次编写相似的代码来为变量赋值,若是其中还包含嵌套结构,只靠遍历是找不到真实信息的,必需要深刻挖掘整个数据结构才能找到所需数据cookie
因此ES6添加了解构功能,将数据结构打散的过程变得更加简单,能够从打散后更小的部分中获取所需信息数据结构
对象字面量的语法形式是在一个赋值操做符左边放置一个对象字面量dom
let node = { type: "Identifier", name: "foo" }; let { type, name } = node; console.log(type); // "Identifier" console.log(name); // "foo"
在这段代码中,node.type的值被存储在名为type的变量中;node.name的值被存储在名为name的变量中函数
【解构赋值】spa
到目前为止,咱们已经将对象解构应用到了变量的声明中。然而,咱们一样能够在给变量赋值时使用解构语法prototype
let node = { type: "Identifier", name: "foo" }, type = "Literal", name = 5; // 使用解构来分配不一样的值 ({ type, name } = node); console.log(type); // "Identifier" console.log(name); // "foo"
在这个示例中,声明变量type和name时初始化了一个值,在后面几行中,经过解构赋值的方法,从node对象读取相应的值从新为这两个变量赋值设计
[注意]必定要用一对小括号包裹解构赋值语句,JS引擎将一对开放的花括号视为一个代码块。语法规定,代码块语句不容许出如今赋值语句左侧,添加小括号后能够将块语句转化为一个表达式,从而实现整个解构赋值过程
解构赋值表达式的值与表达式右侧(也就是=右侧)的值相等,如此一来,在任何可使用值的地方均可以使用解构赋值表达式
let node = { type: "Identifier", name: "foo" }, type = "Literal", name = 5; function outputInfo(value) { console.log(value === node); // true } outputInfo({ type, name } = node); console.log(type); // "Identifier" console.log(name); // "foo"
调用outputlnfo()函数时传入了一个解构表达式,因为JS表达式的值为右侧的值,于是此处传入的参数等同于node,且变量type和name被从新赋值,最终将node传入outputlnfo()函数
[注意]解构赋值表达式(也就是=右侧的表达式)若是为null或undefined会致使程序抛出错误。也就是说,任未尝试读取null或undefined的属性的行为都会触发运行时错误
【默认值】
使用解构赋值表达式时,若是指定的局部变量名称在对象中不存在,那么这个局部变量会被赋值为undefined
let node = { type: "Identifier", name: "foo" }; let { type, name, value } = node; console.log(type); // "Identifier" console.log(name); // "foo" console.log(value); // undefined
这段代码额外定义了一个局部变量value,而后尝试为它赋值,然而在node对象上,没有对应名称的属性值,因此像预期中的那样将它赋值为undefined
当指定的属性不存在时,能够随意定义一个默认值,在属性名称后添加一个等号(=)和相应的默认值便可
let node = { type: "Identifier", name: "foo" }; let { type, name, value = true } = node; console.log(type); // "Identifier" console.log(name); // "foo" console.log(value); // true
在此示例中,为变量value设置了默认值true,只有当node上没有该属性或者该属性值为undefined时该值才生效。此处没有node.value属性,由于value使用了预设的默认值
【为非同名局部变量赋值】
若是但愿使用不一样命名的局部变量来存储对象属性的值,ES6中的一个扩展语法能够知足需求,这个语法与完整的对象字面量属性初始化程序的很像
let node = { type: "Identifier", name: "foo" }; let { type: localType, name: localName } = node; console.log(localType); // "Identifier" console.log(localName); // "foo"
这段代码使用了解构赋值来声明变量localType和localName,这两个变量分别包含node.type和node.name属性的值。type:localType语法的含义是读取名为type的属性并将其值存储在变量localType中,这种语法实际上与传统对象字面量的语法相悖,原来的语法名称在冒号左边,值在右边;如今值在冒号右边,而对象的属性名在左边
当使用其余变量名进行赋值时也能够添加默认值,只需在变量名后添加等号和默认值便可
let node = { type: "Identifier" }; let { type: localType, name: localName = "bar" } = node; console.log(localType); // "Identifier" console.log(localName); // "bar"
在这段代码中,因为node.name属性不存在,变量被默认赋值为"bar"
【嵌套对象解构】
解构嵌套对象仍然与对象字面量的语法类似,能够将对象拆解以获取想要的信息
let node = { type: "Identifier", name: "foo", loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 } } }; let { loc: { start }} = node; console.log(start.line); // 1 console.log(start.column); // 1
在这个示例中,咱们在解构模式中使用了花括号,其含义为在找到node对象中的loc属性后,应当深刻一层继续查找start属性。在上面的解构示例中,全部冒号前的标识符都表明在对象中的检索位置,其右侧为被赋值的变量名;若是冒号后是花括号,则意味着要赋予的最终值嵌套在对象内部更深的层级中
更进一步,也可使用一个与对象属性名不一样的局部变量名
let node = { type: "Identifier", name: "foo", loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 } } }; // 提取 node.loc.start let { loc: { start: localStart }} = node; console.log(localStart.line); // 1 console.log(localStart.column); // 1
在这个版本中,node.loc.start被存储在了新的局部变量localStart中。解构模式能够应用于任意层级深度的对象,且每一层都具有同等的功能
与对象解构的语法相比,数组解构就简单多了,它使用的是数组字面量,且解构操做所有在数组内完成,而不是像对象字面量语法同样使用对象的命名属性
let colors = [ "red", "green", "blue" ]; let [ firstColor, secondColor ] = colors; console.log(firstColor); // "red" console.log(secondColor); // "green"
在这段代码中,咱们从colors数组中解构出了"red"和"green"这两个值,并分别存储在变量firstColor和变量secondColor中。在数组解构语法中,咱们经过值在数组中的位置进行选取,且能够将其存储在任意变量中,未显式声明的元素都会直接被忽略
在解构模式中,也能够直接省略元素,只为感兴趣的元素提供变量名
let colors = [ "red", "green", "blue" ]; let [ , , thirdColor ] = colors; console.log(thirdColor); // "blue"
这段代码使用解构赋值语法从colors中获取第3个元素,thirdColor前的逗号是前方元素的占位符,不管数组中的元素有多少个,均可以经过这种方法提取想要的元素,不须要为每个元素都指定变量名
【解构赋值】
数组解构也可用于赋值上下文,但不须要用小括号包裹表达式,这一点与对象解构不一样
let colors = [ "red", "green", "blue" ], firstColor = "black", secondColor = "purple"; [ firstColor, secondColor ] = colors; console.log(firstColor); // "red" console.log(secondColor); // "green"
这段代码中的解构赋值与上一个数组解构示例相差无几,惟一的区别是此处的firstColor变量和secondColor变量已经被定义了
【变量交换】
数组解构语法还有一个独特的用例:交换两个变量的值。在排序算法中,值交换是一个很是常见的操做,若是要在ES5中交换两个变量的值,则须引入第三个临时变量
// 在 ES5 中互换值 let a = 1, b = 2, tmp; tmp = a; a = b; b = tmp; console.log(a); // 2 console.log(b); // 1
在这种变量交换的方式中,中间变量tmp不可或缺。若是使用数组解构赋值语法,就再也不须要额外的变量了
// 在 ES6 中互换值 let a = 1, b = 2; [ a, b ] = [ b, a ]; console.log(a); // 2 console.log(b); // 1
在这个示例中,数组解构赋值看起来像是一个镜像:赋值语句左侧(也就是等号左侧)与其余数组解构示例同样,是一个解构模式;右侧是一个为交换过程建立的临时数组字面量。代码执行过程当中,先解构临时数组,将b和a的值复制到左侧数组的前两个位置,最终结果是变量互换了它们的值
[注意]若是右侧数组解构赋值表达式的值为null或undefined,则会致使程序抛出错误
【默认值】
也能够在数组解构赋值表达式中为数组中的任意位置添加默认值,当指定位置的属性不存在或其值为undefined时使用默认值
let colors = [ "red" ]; let [ firstColor, secondColor = "green" ] = colors; console.log(firstColor); // "red" console.log(secondColor); // "green"
在这段代码中,colors数组中只有一个元素,secondColor没有对应的匹配值,可是它有一个默认值"green",因此最终secondColor的输出结果不会是undefined
【嵌套数组解构】
嵌套数组解构与嵌套对象解构的语法相似,在原有的数组模式中插入另外一个数组模式,便可将解构过程深刻到下一个层级
let colors = [ "red", [ "green", "lightgreen" ], "blue" ]; // 随后 let [ firstColor, [ secondColor ] ] = colors; console.log(firstColor); // "red" console.log(secondColor); // "green"
在此示例中,变量secondColor引用的是colors数组中的值"green",该元素包含在数组内部的另外一个数组中,因此seconColor两侧的方括号是一个必要的解构模式。一样,在数组中也能够无限深刻去解构,就像在对象中同样
【不定元素】
函数具备不定参数,而在数组解构语法中有一个类似的概念——不定元素。在数组中,能够经过...语法将数组中的其他元素赋值给一个特定的变量
let colors = [ "red", "green", "blue" ]; let [ firstColor, ...restColors ] = colors; console.log(firstColor); // "red" console.log(restColors.length); // 2 console.log(restColors[0]); // "green" console.log(restColors[1]); // "blue"
数组colors中的第一个元素被赋值给了firstColor,其他的元素被赋值给restColors数组,因此restColors中包含两个元素:"green"和"blue"。不定元素语法有助于从数组中提取特定元素并保证其他元素可用
【数组复制】
在ES5中,开发者们常用concat()方法来克隆数组
// 在 ES5 中克隆数组 var colors = [ "red", "green", "blue" ]; var clonedColors = colors.concat(); console.log(clonedColors); //"[red,green,blue]"
concat()方法的设计初衷是链接两个数组,若是调用时不传递参数就会返回当前函数的副本
在ES6中,能够经过不定元素的语法来实现相同的目标
// 在 ES6 中克隆数组 let colors = [ "red", "green", "blue" ]; let [ ...clonedColors ] = colors; console.log(clonedColors); //"[red,green,blue]"
在这个示例中,咱们经过不定元素的语法将colors数组中的值复制到clonedColors数组中
[注意]在被解构的数组中,不定元素必须为最后一个条目,在后面继续添加逗号会致使程序抛出语法错误
能够混合使用对象解构和数组解构来建立更多复杂的表达式,如此一来,能够从任何混杂着对象和数组的数据解构中提取想要的信息
let node = { type: "Identifier", name: "foo", loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 } }, range: [0, 3] }; let { loc: { start }, range: [ startIndex ] } = node; console.log(start.line); // 1 console.log(start.column); // 1 console.log(startIndex); // 0
这段代码分别将node.loc.start和node.range[0]提取到变量start和startlndex中
解构模式中的loc和range仅表明它们在node对象中所处的位置(也就是该对象的属性)。当使用混合解构的语法时,则能够从node提取任意想要的信息。这种方法极为有效,尤为是从JSON配置中提取信息时,再也不须要遍历整个结构了
【解构参数】
解构能够用在函数参数的传递过程当中,这种使用方式更特别。当定义一个接受大量可选参数的JS函数时,一般会建立一个可选对象,将额外的参数定义为这个对象的属性
// options 上的属性表示附加参数 function setCookie(name, value, options) { options = options || {}; let secure = options.secure, path = options.path, domain = options.domain, expires = options.expires; // 设置 cookie 的代码 } // 第三个参数映射到 options setCookie("type", "js", { secure: true, expires: 60000 });
许多JS库中都有相似的setCookie()函数,而在示例函数中,name和value是必需参数,而secure、path、domain和expires则否则,这些参数相对而言没有优先级顺序,将它们列为额外的命名参数也不合适,此时为options对象设置同名的命名属性是一个很好的选择。如今的问题是,仅查看函数的声明部分,没法辨识函数的预期参数,必须经过阅读函数体才能够肯定全部参数的状况
若是将options定义为解构参数,则能够更清晰地了解函数预期传入的参数。解构参数须要使用对象或数组解构模式代替命名参数
function setCookie(name, value, { secure, path, domain, expires }) { // 设置 cookie 的代码 } setCookie("type", "js", { secure: true, expires: 60000 });
这个函数与以前示例中的函数具备类似的特性,只是如今使用解构语法代替了第3个参数来提取必要的信息,其余参数保持不变,可是对于调用setCookie()函数的使用者而言,解构参数变得更清晰了
【必须传值的解构参数】
解构参数有一个奇怪的地方,默认状况下,若是调用函数时不提供被解构的参数会致使程序抛出错误
// 出错! setCookie("type", "js");
缺失的第3个参数,其值为undefined,而解构参数只是将解构声明应用在函数参数的一个简写方法,其会致使程序抛出错误。当调用setCookie()函数时,JS引擎实际上作了如下这些事情
function setCookie(name, value, options) { let { secure, path, domain, expires } = options; // 设置 cookie 的代码 }
若是解构赋值表达式的右值为null或undefined,则程序会报错。同理,若调用setCookie()函数时不传入第3个参数,也会致使程序抛出错误
若是解构参数是必需的,大可忽略掉这些问题;但若是但愿将解构参数定义为可选的,那么就必须为其提供默认值来解决这个问题
function setCookie(name, value, { secure, path, domain, expires } = {}) { // ... }
这个示例中为解构参数添加了一个新对象做为默认值,secure、path、domain及expires这些变量的值所有为undefined,这样即便在调用setCookie()时未传递第3个参数,程序也不会报错
【默认值】
能够为解构参数指定默认值,就像在解构赋值语句中那样,只需在参数后添加等号而且指定一个默认值便可
function setCookie(name, value, { secure = false, path = "/", domain = "example.com", expires = new Date(Date.now() + 360000000) } = {} ) { // ... }
在这段代码中,解构参数的每个属性都有默认值,从而无须再逐一检查每个属性是否都有默认值。然而,这种方法也有不少缺点。首先,函数声明变得比之前复杂了;其次,若是解构参数是可选的,那么仍然要给它添加一个空对象做为参数,不然像setCookie("type","js")这样的调用会致使程序抛出错误
对于对象类型的解构参数,最好为其赋予相同解构的默认参数
function setCookie(name, value, { secure = false, path = "/", domain = "example.com", expires = new Date(Date.now() + 360000000) } = { secure : false, path : "/", domain : "example.com", expires : new Date(Date.now() + 360000000) } ) { // ... }
如今函数变得更加完整了,第一个对象字面量是解构参数,第二个为默认值。可是这会形成很是多的代码冗余,能够将默认值提取到一个独立对象中,而且使用该对象做为解构和默认参数的一部分,从而消除这些冗余
const setCookieDefaults = { secure : false, path : "/", domain : "example.com", expires : new Date(Date.now() + 360000000) } function setCookie(name, value,{ secure = setCookieDefaults.secure, path = setCookieDefaults.path, domain = setCookieDefaults.domain, expires = setCookieDefaults.expires }=setCookieDefaults) { // ... }
在这段代码中,默认值已经被放到setCookieDefaults对象中,除了做为默认参数值外,在解构参数中能够直接使用这个对象来为每个绑定设置默认参数。使用解构参数后,不得不面对处理默认参数的复杂逻辑,但它也有好的一面,若是要改变默认值,能够当即在setCookieDefaults中修改,改变的数据将自动同步到全部出现过的地方
【字符串解构】
字符串也能够解构赋值。这是由于,字符串被转换成了一个相似数组的对象
const [a, b, c, d, e] = 'hello'; console.log(a);//"h" console.log(b);//"e" console.log(c);//"l" console.log(d);//"l" console.log(e);//"o"
相似数组的对象都有一个length
属性,所以还能够对这个属性解构赋值
const {length} = 'hello'; console.log(length);//5
【数值和布尔值解构】
解构赋值时,若是等号右边是数值和布尔值,则会先转为对象
let {toString:s1} = 123; console.log(s1 === Number.prototype.toString);//true let {toString:s2} = true; console.log(s2 === Boolean.prototype.toString);//true
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。因为undefined
和null
没法转为对象,因此对它们进行解构赋值,都会报错
let { prop: x } = undefined; // TypeError let { prop: y } = null; // TypeError