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