編者注解:一個早期 ES6 的版本是由 Firefox 開發(fā)工具的工程師尼克?菲茨杰拉德完成,最初以 ES6 非結構化賦值出現(xiàn)在尼克的博客中。
非結構化賦值允許你通過使用語法規(guī)則將一個數(shù)組或對象的屬性分配給變量,類似于數(shù)組或對象文本。這個語法規(guī)則非常簡單,同時還表現(xiàn)出比傳統(tǒng)的屬性訪問更清晰。
如果沒有非結構化賦值,你可能運用下面的方式訪問數(shù)組中的前三個項目:
var first = someArray[0];
var second = someArray[1];
var third = someArray[2];
當有了非結構化賦值,你可以運用與上面功能等價的非結構化賦值代碼,使得代碼更加簡潔和可讀:
var [first, second, third] = someArray;
SpiderMonkey ( Firefox 提供的 JavaScript 引擎 ) 提供支持大部分的非結構化,但是并不是全部。追蹤 SpiderMonkey 的支持的非結構操作參見:https://bugzilla.mozilla.org/show_bug.cgi?id=694100 。
我們已經看到了基于數(shù)組的非結構化賦值的例子。語法的一般形式是:
[ variable1, variable2, ..., variableN ] = array;
這將會從數(shù)組 array 中對應的項目值賦值給變量1 ( variable1 ) 到變量N ( variableN )。與此同時,如果你想定義你自己的變量,你可以增加 var , let , 或者 const 在賦值語句的最前面。
不像普通的 字符串那樣,模板字符可以覆蓋多行:
ar [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;
事實上,用變量 ( variable ) 來描述是不恰當?shù)?,因為你還可以用深層次的嵌套模式:
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"
并且你可以通過“休息”模式,獲得數(shù)組中所有后續(xù)的項目:
var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]
當你訪問數(shù)組中的項目時,產生越界或者訪問不存在,你會得到同樣的索引結果:未定義的。
console.log([][0]);
// undefined
var [missing] = [];
console.log(missing);
// undefined
注意到基于數(shù)組賦值模式的非結構化賦值通常可以運用在任意的迭代器上:
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"
并且像非結構化數(shù)組賦值一樣,你可以嵌套和結合深層次的非結構化:
var complicatedObj = {
arrayProp: [
"Zapp",
{ second: "Brannigan" }
]
};
var { arrayProp: [first, { second }] } = complicatedObj;
console.log(first);
// "Zapp"
console.log(second);
// "Brannigan"
當你非結構化賦值的屬性沒有被定義,你能得到值:未定義:
var { missing } = {};
console.log(missing);
// undefined
一個潛在的陷阱你應該知道的是當你使用非結構化對象賦值來分配的變量,但是你沒有聲明它們:
{ blowUp } = { blowUp: 10 };
// Syntax error
這將發(fā)生因為 JavaScript 語法告訴引擎去解釋任意的以 { 開頭的塊聲明。(舉個例子, { console } 是一個有效的塊聲明)。 解決方案是把整個表達式的用括號括起來:
({ safe } = {});
// No errors
當你試圖運用非結構化在 null 或者 undefined 上時,你將會得到一個類型錯誤的值:
var {blowUp} = null;
// TypeError: null has no properties
然而,你可以運用非結構化在其它原始的類型中,比如:布爾類型,數(shù)字,和字符串,和未定義 undefinded 類型中:
var {wtf} = NaN;
console.log(wtf);
// undefined
這可能是意想不到的,但在進一步檢查中,其實原因也很簡單。當你運用對象賦值的模式時,被非結構化的值需要被對象支配。大多數(shù)的類型能夠轉換成對象類型,但是 null 和 undefined 不能轉換。當你用數(shù)組賦值模式時,被去結構化的值必須是迭代器的形式。
當你非結構化的屬性值未被定義時,你還可以提供默認值:
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
(編者注釋:這個默認值特性現(xiàn)在已經在 Firfox 中實現(xiàn),但是只支持前兩種形式,不包括第三種。參見 bug 932080 : https://bugzilla.mozilla.org/show_bug.cgi?id=932080 )
作為開發(fā)者,我們通常公開一些 API 通過接受一個有多個屬性的對象作為一個參數(shù),取而代之強制我們的 API 調用者去記住許多個別參數(shù)的順序。我們可以利用去結構化來避免重復這個有單一屬性的對象,當我們要引用其中的一個屬性時:
function removeBreakpoint({ url, line, column }) {
// ...
}
這是一個來自 Firefox 開發(fā)工具的簡化真實世界的代碼( 在 JavaScript 中被實現(xiàn) )。我們發(fā)現(xiàn)這種寫法很受歡迎。
擴展前面的示例,我們也可以用非結構化賦值給對象的屬性設置默認值。當我們有一個負責提供配置信息的對象,該對象的屬性值已經有合理的默認值,用非結構化給對象設置默認值就顯得很有幫助了。舉個例子, jQuery 的 ajax 函數(shù)將一個配置對象作為它的第二個參數(shù),并且被寫為:
jQuery.ajax = function (url, {
async = true,
beforeSend = noop,
cache = true,
complete = noop,
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
這樣做避免了對于這個配置對象的每個屬性操作重復: var foo = config.foo || theDefaultFoo。
( 編者注解:不幸地是,對象的默認值這種速寫語法在 Firefox 中還未實現(xiàn)。我知道,我們已經用了一些段落來學習關于非結構化賦值的知識。最新更新參見 [bug 932080]( https://bugzilla.mozilla.org/show_bug.cgi?id=932080) )
ECMAScript 6 還定義了一個迭代協(xié)議,在這里我們主要討論早起的迭代協(xié)議。當?shù)?Map 類型時,你得到一些列的 [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) {
// ...
}
盡管多個返回值沒有內置到語言本體中,但是也并不需要將返回值內置,因為你可以返回一個數(shù)組或者非結構化結果:
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;
或者使用連續(xù)地傳遞模式:
function returnMultipleValues(k) {
k(1, 2);
}
returnMultipleValues((foo, bar) => ...);
仍然不想用 ES6 模塊?還想用 COMMONJS 模塊?沒問題!當導入一些 COMMONJS 模塊 X,這是很顯然 X 導入一些比你打算使用的函數(shù)更多的函數(shù)。通過非結構化賦值的方式,你可以顯式的導入你需要用到的函數(shù),從面避免命名空間的污染:
const { SourceMapConsumer, SourceNode } = require("source-map");
因此,正如你所看到的,非結構化賦值在許多場景中是有用的。在 Mozilla 我們已經有了很多關于非結構化賦值的經驗。多年前,Lars Hansen 就已經將 JS 非結構化賦值引入到了 opera 中,Brendan Eich 稍后也將在 Firefox 中增加非結構化賦值。它會被引入在 Firefox 2 版本中。因此我們知道非結構化在你每天使用的語言當中,安靜地使你的代碼變得簡潔和清晰。
更新非結構化賦值來遵守 ES6 一直是一個團隊努力的結果。尤其感謝 Tooru Fujisawa (arai) and Arpad Borsos (Swatinem) 的杰出貢獻。
支持非結構化賦值的 Chrome 當前正在開發(fā)與此同時其他瀏覽器無疑也會增加支持非結構化賦值操作?,F(xiàn)在來說,如果你想在 Web 上運用非結構化,你需要用到 Babel 或者 Traceur 。