什么是解構(gòu)賦值
解構(gòu)賦值允許你使用類似數(shù)組或?qū)ο笞置媪康恼Z(yǔ)法將數(shù)組和對(duì)象的屬性賦給各種變量。這種賦值語(yǔ)法極度簡(jiǎn)潔,同時(shí)還比傳統(tǒng)的屬性訪問(wèn)方法更為清晰。
通常來(lái)說(shuō),我們?cè)L問(wèn)數(shù)組的元素的時(shí)候是這樣子的:
var first = someArray[0];
var second = someArray[1];
var third = someArray[2];
使用解構(gòu)賦值賦值則會(huì)變得比較簡(jiǎn)單并且直觀:
var [first, second, third] = someArray;
SpiderMonkey(Firefox的JavaScript引擎)已經(jīng)支持解構(gòu)的大部分功能,但是仍不健全。你可以通過(guò)bug 694100跟蹤解構(gòu)和其它ES6特性在SpiderMonkey中的支持情況。
數(shù)組和迭代器的解構(gòu)
以上是數(shù)組解構(gòu)的簡(jiǎn)單例子,語(yǔ)法的形式為:
// [變量1,變量2,...,變量N] = 數(shù)組
[ variable1, variable2, ..., variableN ] = array;
這將為variable1到variableN的變量賦予數(shù)組中相應(yīng)元素項(xiàng)的值。如果你想在賦值的同時(shí)聲明變量,可在賦值語(yǔ)句前加入var、let或const關(guān)鍵字,例如:
var [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;
事實(shí)上,用變量來(lái)描述并不恰當(dāng),因?yàn)槟憧梢詫?duì)任意深度的嵌套數(shù)組進(jìn)行解構(gòu):
var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo); // => 1
console.log(bar); // => 2
console.log(baz); // => 3
此外,你可以在對(duì)應(yīng)位留空來(lái)跳過(guò)被解構(gòu)數(shù)組中的某些元素:
var [,,third] = ["foo", "bar", "baz"];
console.log(third); // => "baz"
而且你還可以通過(guò)“不定參數(shù)”模式捕獲數(shù)組中的所有尾隨元素:
var [head, ...tail] = [1, 2, 3, 4];
console.log(head); // => 1
console.log(tail); // => [2, 3, 4]
當(dāng)訪問(wèn)空數(shù)組或越界訪問(wèn)數(shù)組時(shí),對(duì)其解構(gòu)與對(duì)其索引的行為一致,最終得到的結(jié)果都是:undefined。
console.log([][0]); // =>undefined
var [missing] = [];
console.log(missing); // => undefined
請(qǐng)注意,數(shù)組解構(gòu)賦值的模式同樣適用于任意迭代器:
function* fibs() { // 生成器函數(shù)
var a = 0;
var b = 1;
while (true) {
yield a; // yield: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield
[a, b] = [b, a + b];
}
}
var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth); // =>5
對(duì)象的解構(gòu)
通過(guò)解構(gòu)對(duì)象,你可以把它的每個(gè)屬性與不同的變量綁定,首先指定被綁定的屬性,然后緊跟一個(gè)要解構(gòu)的變量:
var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };
var { name: nameA } = robotA;
var { name: nameB } = robotB;
console.log(nameA); // => "Bender"
console.log(nameB); // => "Flexo"
當(dāng)屬性名與變量名一致時(shí),可以通過(guò)一種實(shí)用的句法簡(jiǎn)寫(xiě):
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
console.log(foo); // => "lorem"
console.log(bar); // => "ipsum"
與數(shù)組解構(gòu)一樣,你可以隨意嵌套并進(jìn)一步組合對(duì)象解構(gòu):
var complicatedObj = {
arrayProp: [
"Zapp",
{ second: "Brannigan" }
]
};
var { arrayProp: [first, { second }] } = complicatedObj;
console.log(first); // => "Zapp"
console.log(second); // => "Brannigan"
當(dāng)你解構(gòu)一個(gè)未定義的屬性時(shí),得到的值為undefined:
var { missing } = {};
console.log(missing); // => undefined
請(qǐng)注意,當(dāng)你解構(gòu)對(duì)象并賦值給變量時(shí),如果你已經(jīng)聲明或不打算聲明這些變量(即賦值語(yǔ)句前沒(méi)有l(wèi)et、const或var關(guān)鍵字),你應(yīng)該注意這樣一個(gè)潛在的語(yǔ)法錯(cuò)誤:
{ blowUp } = { blowUp: 10 }; // Syntax error 語(yǔ)法錯(cuò)誤
為什么會(huì)出錯(cuò)?這是因?yàn)镴avaScript語(yǔ)法通知解析引擎將任何以{開(kāi)始的語(yǔ)句解析為一個(gè)塊語(yǔ)句(例如,{console}是一個(gè)合法塊語(yǔ)句)。解決方案是將整個(gè)表達(dá)式用一對(duì)小括號(hào)包裹:
({ safe } = {});
這樣就沒(méi)有語(yǔ)法錯(cuò)誤了;
解構(gòu)值不是對(duì)象、數(shù)組或迭代器
當(dāng)你嘗試解構(gòu)null或undefined時(shí),你會(huì)得到一個(gè)類型錯(cuò)誤:
var {blowUp} = null; // TypeError: null has no properties(null沒(méi)有屬性)
然而,你可以解構(gòu)其它原始類型,例如:布爾值、數(shù)值、字符串,但是你將得到undefined:
var {wtf} = NaN;
console.log(wtf); // undefined
你可能對(duì)此感到意外,但經(jīng)過(guò)進(jìn)一步審查你就會(huì)發(fā)現(xiàn),原因其實(shí)非常簡(jiǎn)單。當(dāng)使用對(duì)象賦值模式時(shí),被解構(gòu)的值[需要被強(qiáng)制轉(zhuǎn)換為對(duì)象。大多數(shù)類型都可以被轉(zhuǎn)換為對(duì)象,但null和undefined 卻無(wú)法進(jìn)行轉(zhuǎn)換。當(dāng)使用數(shù)組賦值模式時(shí),被解構(gòu)的值一定要包含一個(gè)迭代器。
默認(rèn)值
當(dāng)你要解構(gòu)的屬性未定義時(shí)你可以提供一個(gè)默認(rèn)值:
var [missing = true] = [];
console.log(missing); // =>true
var { message: msg = "Something went wrong" } = {};
console.log(msg); // => "Something went wrong"
var { x = 3 } = {};
console.log(x); //=> 3
解構(gòu)的實(shí)際應(yīng)用
函數(shù)參數(shù)定義
作為開(kāi)發(fā)者,我們需要實(shí)現(xiàn)設(shè)計(jì)良好的API,通常的做法是為函數(shù)設(shè)計(jì)一個(gè)對(duì)象作為參數(shù),然后將不同的實(shí)際參數(shù)作為對(duì)象屬性,以避免讓API使用者記住 多個(gè)參數(shù)的使用順序。我們可以使用解構(gòu)特性來(lái)避免這種問(wèn)題,當(dāng)我們想要引用它的其中一個(gè)屬性時(shí),大可不必反復(fù)使用這種單一參數(shù)對(duì)象。
function removeBreakpoint({ url, line, column }) {
// ...
}
這是一段來(lái)自Firefox開(kāi)發(fā)工具JavaScript調(diào)試器(同樣使用JavaScript實(shí)現(xiàn))的代碼片段,它看起來(lái)非常簡(jiǎn)潔,我們會(huì)發(fā)現(xiàn)這種代碼模式特別討喜。
配置對(duì)象參數(shù)
延伸一下之前的示例,我們同樣可以給需要解構(gòu)的對(duì)象屬性賦予默認(rèn)值。當(dāng)我們構(gòu)造一個(gè)提供配置的對(duì)象,并且需要這個(gè)對(duì)象的屬性攜帶默認(rèn)值時(shí),解構(gòu)特性就派上用場(chǎng)了。舉個(gè)例子,jQuery的ajax函數(shù)使用一個(gè)配置對(duì)象作為它的第二參數(shù),我們可以這樣重寫(xiě)函數(shù)定義:
jQuery.ajax = function (url, {
async = true,
beforeSend = noop,
cache = true,
complete = noop,
crossDomain = false,
global = true,
// ... 更多配置
}) {
// ... do stuff
};
如此一來(lái),我們可以避免對(duì)配置對(duì)象的每個(gè)屬性都重復(fù)var foo = config.foo || theDefaultFoo;這樣的操作(可以設(shè)置默認(rèn)值)。
與ES6迭代器協(xié)議協(xié)同使用
ECMAScript 6中定義了一個(gè)迭代器協(xié)議,我們?cè)凇?a target="_blank" rel="nofollow">深入淺出ES6(二):迭代器和for-of循環(huán)》中已經(jīng)詳細(xì)解析過(guò)。當(dāng)你迭代Maps(ES6標(biāo)準(zhǔn)庫(kù)中新加入的一種對(duì)象)后,你可以得到一系列形如[key, value]
的鍵值對(duì),我們可將這些鍵值對(duì)解構(gòu),更輕松地訪問(wèn)鍵和值:
var map = new Map();
map.set(window, "the global");
map.set(document, "the document");
for (var [key, value] of map) {
console.log(key + " is " + value);
}
// "[object Window] is the global"
// "[object HTMLDocument] is the document"
只遍歷鍵:
for (var [key] of map) {
// ...
}
或只遍歷值:
for (var [,value] of map) {
// ...
}
多重返回值
JavaScript語(yǔ)言中尚未整合多重返回值的特性,但是無(wú)須多此一舉,因?yàn)槟阕约壕涂梢苑祷匾粋€(gè)數(shù)組并將結(jié)果解構(gòu):
function returnMultipleValues() {
return [1, 2];
}
var [foo, bar] = returnMultipleValues();
或者,你可以用一個(gè)對(duì)象作為容器并為返回值命名:
function returnMultipleValues() {
return {
foo: 1,
bar: 2
};
}
var { foo, bar } = returnMultipleValues();
這兩個(gè)模式都比額外保存一個(gè)臨時(shí)變量要好得多。
function returnMultipleValues() {
return {
foo: 1,
bar: 2
};
}
var temp = returnMultipleValues();
var foo = temp.foo;
var bar = temp.bar;
或者使用CPS變換:
function returnMultipleValues(k) {
k(1, 2);
}
returnMultipleValues((foo, bar) => ...);
使用解構(gòu)導(dǎo)入部分CommonJS模塊
你是否尚未使用ES6模塊?還用著CommonJS的模塊呢吧!沒(méi)問(wèn)題,當(dāng)我們導(dǎo)入CommonJS模塊X時(shí),很可能在模塊X中導(dǎo)出了許多你根本沒(méi)打算用的函數(shù)。通過(guò)解構(gòu),你可以顯式定義模塊的一部分來(lái)拆分使用,同時(shí)還不會(huì)污染你的命名空間:
const { SourceMapConsumer, SourceNode } = require("source-map");
(如果你使用ES6模塊,你一定知道在import聲明中有一個(gè)相似的語(yǔ)法。)
解構(gòu)在許多獨(dú)立小場(chǎng)景中非常實(shí)用。在Mozilla我們已經(jīng)積累了許多有關(guān)解構(gòu)的使用經(jīng)驗(yàn)。十年前,Lars Hansen在Opera中引入了JS解構(gòu)特性,Brendan Eich隨后就給Firefox也增加了相應(yīng)的支持,移植時(shí)版本為Firefox 2。所以我們可以肯定,漸漸地,你會(huì)在每天使用的語(yǔ)言中加入解構(gòu)這個(gè)新特性,它可以讓你的代碼變得更加精簡(jiǎn)整潔。
本文引自:http://www.infoq.com/cn/articles/es6-in-depth-destructuring/