什麼是解構賦值?
解構賦值允許你使用類似數組或對象字面量的語法將數組和對象的屬性賦給各種變量。這種賦值語法極度簡潔,同時還比傳統的屬性訪問方法更爲清晰。
通常來說,你很可能這樣訪問數組中的前三個元素:
var first = someArray[0];
var second = someArray[1];
var third = someArray[2];
如果使用解構賦值的特性,將會使等效的代碼變得更加簡潔並且可讀性更高:
var [first, second, third] = someArray;
SpiderMonkey(Firefox的JavaScript引擎)已經支持解構的大部分功能,但是仍不健全。你可以通過bug 694100跟蹤解構和其它ES6特性在SpiderMonkey中的支持情況。
數組與迭代器的解構
以上是數組解構賦值的一個簡單示例,其語法的一般形式爲:
[ variable1, variable2, ..., variableN ] = array;
這將爲variable1到variableN的變量賦予數組中相應元素項的值。如果你想在賦值的同時聲明變量,可在賦值語句前加入var
、let
或const
關鍵字,例如:
var [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;
事實上,用變量
來描述並不恰當,因爲你可以對任意深度的嵌套數組進行解構:
var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
// 1
console.log(bar);
// 2
console.log(baz);
// 3
此外,你可以在對應位留空來跳過被解構數組中的某些元素:
var [,,third] = ["foo", "bar", "baz"];
console.log(third);
// "baz"
而且你還可以通過“不定參數”模式捕獲數組中的所有尾隨元素:
var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]
當訪問空數組或越界訪問數組時,對其解構與對其索引的行爲一致,最終得到的結果都是:undefined
。
console.log([][0]);
// undefined
var [missing] = [];
console.log(missing);
// undefined
請注意,數組解構賦值的模式同樣適用於任意迭代器:
function* fibs() {
var a = 0;
var b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth);
// 5
對象的解構
通過解構對象,你可以把它的每個屬性與不同的變量綁定,首先指定被綁定的屬性,然後緊跟一個要解構的變量。
var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };
var { name: nameA } = robotA;
var { name: nameB } = robotB;
console.log(nameA);
// "Bender"
console.log(nameB);
// "Flexo"
當屬性名與變量名一致時,可以通過一種實用的句法簡寫:
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
console.log(foo);
// "lorem"
console.log(bar);
// "ipsum"
與數組解構一樣,你可以隨意嵌套並進一步組合對象解構:
var complicatedObj = {
arrayProp: [
"Zapp",
{ second: "Brannigan" }
]
};
var { arrayProp: [first, { second }] } = complicatedObj;
console.log(first);
// "Zapp"
console.log(second);
// "Brannigan"
當你解構一個未定義的屬性時,得到的值爲undefined
:
var { missing } = {};
console.log(missing);
// undefined
請注意,當你解構對象並賦值給變量時,如果你已經聲明或不打算聲明這些變量(亦即賦值語句前沒有let
、const
或var
關鍵字),你應該注意這樣一個潛在的語法錯誤:
{ blowUp } = { blowUp: 10 };
// Syntax error 語法錯誤
爲什麼會出錯?這是因爲JavaScript語法通知解析引擎將任何以{開始的語句解析爲一個塊語句(例如,{console}
是一個合法塊語句)。解決方案是將整個表達式用一對小括號包裹:
({ safe } = {});
// No errors 沒有語法錯誤
解構值不是對象、數組或迭代器
當你嘗試解構null
或undefined
時,你會得到一個類型錯誤:
var {blowUp} = null;
// TypeError: null has no properties(null沒有屬性)
然而,你可以解構其它原始類型,例如:布爾值
、數值
、字符串
,但是你將得到undefined
:
var {wtf} = NaN;
console.log(wtf);
// undefined
你可能對此感到意外,但經過進一步審查你就會發現,原因其實非常簡單。當使用對象賦值模式時,被解構的值需要被強制轉換爲對象。大多數類型都可以被轉換爲對象,但null
和undefined
卻無法進行轉換。當使用數組賦值模式時,被解構的值一定要包含一個迭代器。
默認值
當你要解構的屬性未定義時你可以提供一個默認值:
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
(譯者按:Firefox目前只實現了這個特性的前兩種情況,第三種尚未實現。詳情查看bug 932080。)
解構的實際應用
函數參數定義
作 爲開發者,我們需要實現設計良好的API,通常的做法是爲函數爲函數設計一個對象作爲參數,然後將不同的實際參數作爲對象屬性,以避免讓API使用者記住 多個參數的使用順序。我們可以使用解構特性來避免這種問題,當我們想要引用它的其中一個屬性時,大可不必反覆使用這種單一參數對象。
function removeBreakpoint({ url, line, column }) {
// ...
}
這是一段來自Firefox開發工具JavaScript調試器(同樣使用JavaScript實現——沒錯,就是這樣!)的代碼片段,它看起來非常簡潔,我們會發現這種代碼模式特別討喜。
配置對象參數
延伸一下之前的示例,我們同樣可以給需要解構的對象屬性賦予默認值。當我們構造一個提供配置的對象,並且需要這個對象的屬性攜帶默認值時,解構特性就派上用場了。舉個例子,jQuery的ajax
函數使用一個配置對象作爲它的第二參數,我們可以這樣重寫函數定義:
jQuery.ajax = function (url, {
async = true,
beforeSend = noop,
cache = true,
complete = noop,
crossDomain = false,
global = true,
// ... 更多配置
}) {
// ... do stuff
};
如此一來,我們可以避免對配置對象的每個屬性都重複var foo = config.foo || theDefaultFoo;
這樣的操作。
(編者按:不幸的是,對象的默認值簡寫語法仍未在Firefox中實現,我知道,上一個編者按後的幾個段落講解的就是這個特性。點擊bug 932080查看最新詳情。)
與ES6迭代器協議協同使用
ECMAScript 6中定義了一個迭代器協議,我們在《深入淺出ES6(二):迭代器和for-of循環》中已經詳細解析過。當你迭代Maps(ES6標準庫中新加入的一種對象)後,你可以得到一系列形如[key,
value]
的鍵值對,我們可將這些鍵值對解構,更輕鬆地訪問鍵和值:
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語言中尚未整合多重返回值的特性,但是無須多此一舉,因爲你自己就可以返回一個數組並將結果解構:
function returnMultipleValues() {
return [1, 2];
}
var [foo, bar] = returnMultipleValues();
或者,你可以用一個對象作爲容器併爲返回值命名:
function returnMultipleValues() {
return {
foo: 1,
bar: 2
};
}
var { foo, bar } = returnMultipleValues();
這兩個模式都比額外保存一個臨時變量要好得多。
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) => ...);
使用解構導入部分CommonJS模塊
你是否尚未使用ES6模塊?還用着CommonJS的模塊呢吧!沒問題,當我們導入CommonJS模塊X時,很可能在模塊X中導出了許多你根本沒打算用的函數。通過解構,你可以顯式定義模塊的一部分來拆分使用,同時還不會污染你的命名空間:
const { SourceMapConsumer, SourceNode } = require("source-map");
(如果你使用ES6模塊,你一定知道在import
聲明中有一個相似的語法。)
文後盤點
所以,正如你所見,解構在許多獨立小場景中非常實用。在Mozilla我們已經積累了許多有關解構的使用經驗。十年前,Lars Hansen在Opera中引入了JS解構特性,Brendan Eich隨後就給Firefox也增加了相應的支持,移植時版本爲Firefox 2。所以我們可以肯定,漸漸地,你會在每天使用的語言中加入解構這個新特性,它可以讓你的代碼變得更加精簡整潔。