JavaScript 中使用最多的兩個資料結構是 Object
和 Array
。
- 物件允許我們建立一個單一實體,其中儲存資料項目,並以鍵值來存取。
- 陣列允許我們將資料項目收集到一個順序清單中。
然而,當我們將這些資料傳遞給函式時,我們可能不需要全部。函式可能只需要某些元素或屬性。
解構賦值 是一種特殊的語法,允許我們將陣列或物件「解開」成一堆變數,因為有時這樣會更方便。
解構也適用於具有許多參數、預設值等複雜函式。我們很快就會看到。
陣列解構
以下是一個範例,說明如何將陣列解構為變數
// we have an array with a name and surname
let arr = ["John", "Smith"]
// destructuring assignment
// sets firstName = arr[0]
// and surname = arr[1]
let [firstName, surname] = arr;
alert(firstName); // John
alert(surname); // Smith
現在我們可以使用變數,而不是陣列成員。
當與 split
或其他回傳陣列的方法結合使用時,看起來很棒
let [firstName, surname] = "John Smith".split(' ');
alert(firstName); // John
alert(surname); // Smith
如你所見,語法很簡單。不過有幾個特殊的細節。讓我們看更多範例來更了解它。
它稱為「解構賦值」,因為它透過將項目複製到變數中來「解構」。然而,陣列本身並未修改。
這只是寫作的一種簡短方式
// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];
陣列中不需要的元素也可以透過額外的逗號捨棄
// second element is not needed
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
在上面的程式碼中,陣列的第二個元素被跳過,第三個元素被指定給 title
,而陣列項目的其餘部分也被跳過(因為它們沒有變數)。
…實際上,我們可以使用它與任何可迭代物件一起使用,而不仅仅是陣列
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
這樣做是因為在內部,解構賦值會透過迭代右側的值來運作。這是一種語法糖,用於呼叫右側 =
的 for..of
並指定值。
我們可以在左側使用任何「可指定」的內容。
例如,物件屬性
let user = {};
[user.name, user.surname] = "John Smith".split(' ');
alert(user.name); // John
alert(user.surname); // Smith
在上一章中,我們看到了 Object.entries(obj) 方法。
我們可以使用它與解構一起迴圈物件的鍵和值
let user = {
name: "John",
age: 30
};
// loop over the keys-and-values
for (let [key, value] of Object.entries(user)) {
alert(`${key}:${value}`); // name:John, then age:30
}
Map
的類似程式碼較為簡單,因為它是可迭代的
let user = new Map();
user.set("name", "John");
user.set("age", "30");
// Map iterates as [key, value] pairs, very convenient for destructuring
for (let [key, value] of user) {
alert(`${key}:${value}`); // name:John, then age:30
}
有一個眾所周知的技巧,可以使用解構賦值來交換兩個變數的值
let guest = "Jane";
let admin = "Pete";
// Let's swap the values: make guest=Pete, admin=Jane
[guest, admin] = [admin, guest];
alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!)
在這裡,我們建立一個由兩個變數組成的暫時陣列,並立即以交換的順序解構它。
我們可以透過這種方式交換兩個以上的變數。
其餘的「…」
通常,如果陣列比左側的清單長,則會省略「額外」的項目。
例如,這裡只取兩個項目,而其餘的項目則被忽略
let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(name1); // Julius
alert(name2); // Caesar
// Further items aren't assigned anywhere
如果我們還想收集所有後續項目,我們可以新增另一個參數,使用三個點 "..."
來取得「其餘」的項目
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// rest is an array of items, starting from the 3rd one
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
rest
的值是陣列中剩餘陣列元素的陣列。
我們可以在 rest
的位置使用任何其他變數名稱,只要確保它之前有三個點,並且在解構賦值中最後出現即可。
let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// now titles = ["Consul", "of the Roman Republic"]
預設值
如果陣列比左側的變數清單短,則不會有錯誤。不存在的值會被視為未定義
let [firstName, surname] = [];
alert(firstName); // undefined
alert(surname); // undefined
如果我們想要一個「預設」值來取代遺失的值,我們可以使用 =
提供它
// default values
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius (from array)
alert(surname); // Anonymous (default used)
預設值可以是更複雜的表達式,甚至是函式呼叫。它們只會在未提供值時評估。
例如,這裡我們對兩個預設值使用 prompt
函式
// runs only prompt for surname
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];
alert(name); // Julius (from array)
alert(surname); // whatever prompt gets
請注意:prompt
僅會對遺失的值(surname
)執行。
物件解構
解構賦值也適用於物件。
基本語法為
let {var1, var2} = {var1:…, var2:…}
我們應該在右側有一個現有的物件,我們想要將其拆分為變數。左側包含一個類似物件的「模式」,用於對應的屬性。在最簡單的情況下,這是一個在 {...}
中的變數名稱清單。
例如
let options = {
title: "Menu",
width: 100,
height: 200
};
let {title, width, height} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
屬性 options.title
、options.width
和 options.height
分別指定給對應的變數。
順序無關緊要。這也行得通
// changed the order in let {...}
let {height, width, title} = { title: "Menu", height: 200, width: 100 }
左側的模式可能會更複雜,並指定屬性和變數之間的對應關係。
如果我們想要將一個屬性指定給另一個名稱的變數,例如,讓 options.width
進入名為 w
的變數,那麼我們可以使用冒號來設定變數名稱
let options = {
title: "Menu",
width: 100,
height: 200
};
// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;
// width -> w
// height -> h
// title -> title
alert(title); // Menu
alert(w); // 100
alert(h); // 200
冒號表示「什麼 : 到哪裡」。在上面的範例中,屬性 width
到 w
,屬性 height
到 h
,而 title
指定給同一個名稱。
對於可能遺失的屬性,我們可以使用 "="
設定預設值,如下所示
let options = {
title: "Menu"
};
let {width = 100, height = 200, title} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
就像陣列或函式參數一樣,預設值可以是任何表達式,甚至是函式呼叫。如果未提供值,它們將會被評估。
在下面的程式碼中,prompt
詢問 width
,但沒有詢問 title
let options = {
title: "Menu"
};
let {width = prompt("width?"), title = prompt("title?")} = options;
alert(title); // Menu
alert(width); // (whatever the result of prompt is)
我們也可以同時結合冒號和等號
let options = {
title: "Menu"
};
let {width: w = 100, height: h = 200, title} = options;
alert(title); // Menu
alert(w); // 100
alert(h); // 200
如果我們有一個具有許多屬性的複雜物件,我們可以只擷取我們需要的部分
let options = {
title: "Menu",
width: 100,
height: 200
};
// only extract title as a variable
let { title } = options;
alert(title); // Menu
剩餘模式「…」
如果物件的屬性比我們的變數多怎麼辦?我們可以取一些,然後將「剩餘」指定到某個地方嗎?
我們可以使用剩餘模式,就像我們對陣列所做的那樣。它不受一些較舊的瀏覽器支援(IE,使用 Babel 來進行 polyfill),但在現代瀏覽器中可以使用。
它看起來像這樣
let options = {
title: "Menu",
height: 200,
width: 100
};
// title = property named title
// rest = object with the rest of properties
let {title, ...rest} = options;
// now title="Menu", rest={height: 200, width: 100}
alert(rest.height); // 200
alert(rest.width); // 100
let
,那就完了在上面的範例中,變數直接在賦值中宣告:let {…} = {…}
。當然,我們也可以使用現有的變數,而不用 let
。但有一個陷阱。
這不會起作用
let title, width, height;
// error in this line
{title, width, height} = {title: "Menu", width: 200, height: 100};
問題在於 JavaScript 將主程式碼流程中的 {...}
(不在另一個表達式中)視為程式碼區塊。這樣的程式碼區塊可用於將陳述式分組,如下所示
{
// a code block
let message = "Hello";
// ...
alert( message );
}
所以這裡 JavaScript 假設我們有一個程式碼區塊,這就是為什麼會發生錯誤。我們想要的是解構。
為了向 JavaScript 顯示它不是程式碼區塊,我們可以用括號 (...)
包住表達式
let title, width, height;
// okay now
({title, width, height} = {title: "Menu", width: 200, height: 100});
alert( title ); // Menu
巢狀解構
如果物件或陣列包含其他巢狀物件和陣列,我們可以使用更複雜的左側模式來擷取更深入的部分。
在下方程式碼中,options
在 size
屬性中具有另一個物件,在 items
屬性中具有陣列。指派左側的模式具有相同的結構,以從中擷取值
let options = {
size: {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true
};
// destructuring assignment split in multiple lines for clarity
let {
size: { // put size here
width,
height
},
items: [item1, item2], // assign items here
title = "Menu" // not present in the object (default value is used)
} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
alert(item1); // Cake
alert(item2); // Donut
options
物件的所有屬性,除了左側沒有的 extra
,都指派給對應的變數
最後,我們從預設值取得 width
、height
、item1
、item2
和 title
。
請注意,size
和 items
沒有變數,因為我們取用它們的內容。
智慧型函式參數
有時函式有許多參數,其中大部分都是選用的。這在使用者介面中特別常見。想像一個建立選單的函式。它可能具有寬度、高度、標題、項目清單等。
以下是撰寫此類函式的錯誤方式
function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
// ...
}
在實際情況中,問題是如何記住參數的順序。通常,IDE 會嘗試協助我們,特別是如果程式碼有良好的文件說明,但還是…另一個問題是如何在大部分參數預設為確定時呼叫函式。
像這樣嗎?
// undefined where default values are fine
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
很醜陋。當我們處理更多參數時,會變得難以閱讀。
解構來救援!
我們可以將參數傳遞為物件,而函式會立即將它們解構為變數
// we pass object to function
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
// ...and it immediately expands it to variables
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
// title, items – taken from options,
// width, height – defaults used
alert( `${title} ${width} ${height}` ); // My Menu 200 100
alert( items ); // Item1, Item2
}
showMenu(options);
我們也可以使用更複雜的解構,包含巢狀物件和冒號對應
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
function showMenu({
title = "Untitled",
width: w = 100, // width goes to w
height: h = 200, // height goes to h
items: [item1, item2] // items first element goes to item1, second to item2
}) {
alert( `${title} ${w} ${h}` ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
完整的語法與解構指派相同
function({
incomingProperty: varName = defaultValue
...
})
然後,對於參數物件,將會有變數 varName
對應屬性 incomingProperty
,預設為 defaultValue
。
請注意,此類解構假設 showMenu()
確實有參數。如果我們要使用所有預設值,則應指定一個空物件
showMenu({}); // ok, all values are default
showMenu(); // this would give an error
我們可以透過將 {}
設定為參數整個物件的預設值來修正此問題
function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
alert( `${title} ${width} ${height}` );
}
showMenu(); // Menu 100 200
在上方程式碼中,整個參數物件預設為 {}
,因此總是有東西可以解構。
摘要
-
解構賦值允許立即將物件或陣列對應到多個變數。
-
完整的物件語法
let {prop : varName = defaultValue, ...rest} = object
這表示屬性
prop
應放入變數varName
中,如果沒有此屬性,則應使用default
值。沒有對應的物件屬性會複製到
rest
物件中。 -
完整的陣列語法
let [item1 = defaultValue, item2, ...rest] = array
第一個項目會放入
item1
中;第二個會放入item2
中,而所有其他項目會組成陣列rest
。 -
可以從巢狀陣列/物件中萃取資料,為此,左側必須與右側具有相同的結構。
留言
<code>
標籤;若要插入數行程式碼,請將它們包覆在<pre>
標籤中;若要插入超過 10 行程式碼,請使用沙盒 (plnkr、jsbin、codepen…)