什麼是解構賦值?
解構賦值允許你使用類似陣列或物件字面量的語法將陣列和物件的屬性值賦給一系列變數。這個語法非常簡潔,而且比傳統的屬性訪問更加清晰。
在不使用解構賦值的情況下,訪問陣列的前三項:
1 2 3 |
var first = someArray[0]; var second = someArray[1]; var third = someArray[2]; |
使用解構賦值後,相應的程式碼變得更簡潔和可讀:
1 |
var [first, second, third] = someArray; |
SpiderMonkey(Firefox 的 JavaScript 引擎)已經支援解構賦值的大部分特性,但還不完全。
陣列和可迭代物件的解構賦值
上面我們已經看到了陣列解構賦值的例子,該語法的一般形式是:
1 |
[ variable1, variable2, ..., variableN ] = array; |
這將把陣列中對應的項依次賦給 variable1
到 variableN
,如果同時需要宣告變數,可以在解構表示式前面新增 var
,let
或 const
關鍵字。
1 2 3 |
var [ variable1, variable2, ..., variableN ] = array; let [ variable1, variable2, ..., variableN ] = array; const [ variable1, variable2, ..., variableN ] = array; |
事實上,你還可以巢狀任意的深度:
1 2 3 4 5 6 7 |
var [foo, [[bar], baz]] = [1, [[2], 3]]; console.log(foo); // 1 console.log(bar); // 2 console.log(baz); // 3 |
此外,還可以跳過陣列中的某些項:
1 2 3 |
var [,,third] = ["foo", "bar", "baz"]; console.log(third); // "baz" |
你還可以用一個 Rest 表示式來捕獲陣列中的剩餘項:
1 2 3 |
var [head, ...tail] = [1, 2, 3, 4]; console.log(tail); // [2, 3, 4] |
如果陣列越界或訪問陣列中不存在的項,將得到和通過陣列索引訪問一樣的值:undefined
。
1 2 3 4 5 6 |
console.log([][0]); // undefined var [missing] = []; console.log(missing); // undefined |
注意,陣列解構賦值的方式也同樣適用於可遍歷的物件:
1 2 3 4 5 6 7 8 9 10 11 12 |
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 |
物件的解構賦值
物件的解構賦值允許你將變數繫結到物件不同的屬性值。指定被繫結的屬性名,後面緊跟要繫結的變數:
1 2 3 4 5 6 7 8 9 10 |
var robotA = { name: "Bender" }; var robotB = { name: "Flexo" }; var { name: nameA } = robotA; var { name: nameB } = robotB; console.log(nameA); // "Bender" console.log(nameB); // "Flexo" |
當繫結的屬性名和接收屬性值的變數名一樣時,還有一個語法糖:
1 2 3 4 5 |
var { foo, bar } = { foo: "lorem", bar: "ipsum" }; console.log(foo); // "lorem" console.log(bar); // "ipsum" |
與陣列一樣,也可以巢狀:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var complicatedObj = { arrayProp: [ "Zapp", { second: "Brannigan" } ] }; var { arrayProp: [first, { second }] } = complicatedObj; console.log(first); // "Zapp" console.log(second); // "Brannigan" |
解構一個不存在的屬性時,將得到 undefined
:
1 2 3 |
var { missing } = {}; console.log(missing); // undefined |
使用物件的解構賦值時還有一個潛在的陷阱,在解構賦值時沒有宣告變數(沒有 var
、let
或 const
關鍵字):
1 2 |
{ blowUp } = { blowUp: 10 }; // Syntax error |
這是因為 JavaScript 語法告訴引擎任何以 {
開始的語句都是語句塊(例如,{console}
就是一個合法的語句塊),解決方法是將整個語句用一對括號包裹:
1 2 |
({ safe } = {}); // No errors |
其他情況
當你嘗試解構 null
或 undefined
,你將得到型別錯誤:
1 2 |
var {blowUp} = null; // TypeError: null has no properties |
不過,你可以對其他基本型別(Boolean、String 和 Number)進行解構,將得到 undefined
:
1 2 3 |
var {wtf} = NaN; console.log(wtf); // undefined |
結果也許會讓你感到意外,但深究一下,其實原因很簡單。在進行物件解構賦值時,被解構的物件將被強制轉換為 Object
,除 null
和 undefined
外,其它型別都可以被強制轉換為物件。進行陣列的結構賦值時,要求被解構的物件有一個遍歷器。
預設值
可以為不存在的屬性指定一個預設值:
1 2 3 4 5 6 7 8 9 10 11 |
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 |
實際應用
函式引數
作為開發人員,我們經常把一個包含多個屬性的物件作為函式的引數,來實現更靈活的 API,而不是讓 API 的使用者記住一些特定順序的引數。我們可以使用物件的解構賦值,來避免每次使用引數時的屬性訪問:
1 2 3 |
function removeBreakpoint({ url, line, column }) { // ... } |
配置物件
完善上面的例子,我們可以為要被解構的物件屬性提供預設值,這在對那些作為配置引數的物件非常實用,因為許多配置項都有一個合理的預設值。例如,jQuery 的 ajax
方法的第二個引數為一個配置物件,我們可以這樣實現:
1 2 3 4 5 6 7 8 9 10 11 |
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;
。
與迭代器一起使用
當遍歷 Map 物件時,我們可以使用解構賦值來遍歷 [key, value]
:
1 2 3 4 5 6 7 8 9 |
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" |
只遍歷鍵:
1 2 3 |
for (var [key] of map) { // ... } |
只遍歷值:
1 2 3 |
for (var [,value] of map) { // ... } |
返回多個值
返回一個陣列,通過解構賦值提取到返回值:
1 2 3 4 |
function returnMultipleValues() { return [1, 2]; } var [foo, bar] = returnMultipleValues(); |
或者,返回一個鍵值對的物件:
1 2 3 4 5 6 7 |
function returnMultipleValues() { return { foo: 1, bar: 2 }; } var { foo, bar } = returnMultipleValues(); |
這兩者都比使用中間變數好:
1 2 3 4 5 6 7 8 9 |
function returnMultipleValues() { return { foo: 1, bar: 2 }; } var temp = returnMultipleValues(); var foo = temp.foo; var bar = temp.bar; |
採用延續式:
1 2 3 4 |
function returnMultipleValues(k) { k(1, 2); } returnMultipleValues((foo, bar) => ...); |
匯入 CommonJS 模組的指定部分
還沒使用過 ES6 的模組吧,那至少使用過 CommonJS 吧。當匯入一個 CommonJS 模組 X 時,模組提供的方法也許多餘你實際使用的。使用解構賦值,你可以明確指定你需要使用模組的哪些部分:
1 |
const { SourceMapConsumer, SourceNode } = require("source-map"); |
如果你使用 ES6 的模組機制,你可以看到 import 宣告時有一個類似的語法。
結論
我們看到,解構賦值在很多場景下都很實用。在 Mozilla,我們已經有很多經驗。Lars Hansen 在 10 年前就向 Opera 引入瞭解構賦值,Brendan Eich 在稍微晚點也給 Firefox 新增了支援,最早出現在 Firefox 2 中。因此,解構賦值已經滲透到我們每天對 JS 的使用中,悄悄地使我們的程式碼更簡短、整潔。
幾周之前,我說過 ES6 將改變我們的編碼方式。這些簡單地改進,可以在幾分鐘內就掌握使用,這些改進正是我們需要的。總的來說,它們最終將影響我們日常的每項工作,是一種革命式進化。
開發版的 Chrome 已經支援解構賦值,毋庸置疑,其他瀏覽器在不久將來也會陸續支援。如果現在你想使用該特性,你可以使用 Babel 或 Traceur 這兩個編譯器。