undefined
和 null
進行物件解構使用一般指定時,您一次只會萃取一塊資料,例如
const arr = ['a', 'b', 'c'];
const x = arr[0]; // extract
const y = arr[1]; // extract
使用解構時,您可以透過接收資料的位置中的模式,同時萃取多塊資料。上一個程式碼中 =
的左手邊就是其中一個位置。在以下程式碼中,A 行中的方括弧是一個解構模式
const arr = ['a', 'b', 'c'];
const [x, y] = arr; // (A)
.equal(x, 'a');
assert.equal(y, 'b'); assert
此程式碼與上一個程式碼執行相同的工作。
請注意,模式「小於」資料:我們只萃取我們需要的部分。
為了了解什麼是解構,請考慮 JavaScript 有兩種相反的運算
建構資料如下所示
// Constructing: one property at a time
const jane1 = {};
.first = 'Jane';
jane1.last = 'Doe';
jane1
// Constructing: multiple properties
const jane2 = {
first: 'Jane',
last: 'Doe',
;
}
.deepEqual(jane1, jane2); assert
萃取資料如下所示
const jane = {
first: 'Jane',
last: 'Doe',
;
}
// Extracting: one property at a time
const f1 = jane.first;
const l1 = jane.last;
.equal(f1, 'Jane');
assert.equal(l1, 'Doe');
assert
// Extracting: multiple properties (NEW!)
const {first: f2, last: l2} = jane; // (A)
.equal(f2, 'Jane');
assert.equal(l2, 'Doe'); assert
A 行中的運算很新:我們宣告兩個變數 f2
和 l2
,並透過解構(多值萃取)初始化它們。
A 行的以下部分是一個解構模式
first: f2, last: l2} {
解構模式在語法上類似於用於多值建構的文字。但它們出現在接收資料的地方(例如,指定左手邊),而不是建立資料的地方(例如,指定右手邊)。
解構模式可以用於「資料接收位置」,例如
變數宣告
const [a] = ['x'];
.equal(a, 'x');
assert
let [b] = ['y'];
.equal(b, 'y'); assert
指定
let b;
= ['z'];
[b] .equal(b, 'z'); assert
參數定義
const f = ([x]) => x;
.equal(f(['a']), 'a'); assert
請注意,變數宣告包括 for-of
迴圈中的 const
和 let
宣告
const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
console.log(index, element);
}// Output:
// 0, 'a'
// 1, 'b'
在接下來的兩個區段中,我們將深入探討兩種解構:物件解構和陣列解構。
物件解構讓您可以透過看起來像物件文字的模式,批次萃取屬性的值
const address = {
street: 'Evergreen Terrace',
number: '742',
city: 'Springfield',
state: 'NT',
zip: '49007',
;
}
const { street: s, city: c } = address;
.equal(s, 'Evergreen Terrace');
assert.equal(c, 'Springfield'); assert
您可以將模式視為一個透明的紙張,您將它放在資料上:模式鍵 'street'
在資料中有一個匹配。因此,資料值 'Evergreen Terrace'
指定給模式變數 s
。
您也可以對基本值進行物件解構
const {length: len} = 'abc';
.equal(len, 3); assert
您也可以對陣列進行物件解構
const {0:x, 2:y} = ['a', 'b', 'c'];
.equal(x, 'a');
assert.equal(y, 'c'); assert
為什麼這樣做有效?陣列索引也是屬性。
物件文字支援屬性值速記,物件模式也支援
const { street, city } = address;
.equal(street, 'Evergreen Terrace');
assert.equal(city, 'Springfield'); assert
練習:物件解構
exercises/destructuring/object_destructuring_exrc.mjs
在物件文字中,你可以有展開屬性。在物件模式中,你可以有剩餘屬性(必須放在最後)
const obj = { a: 1, b: 2, c: 3 };
const { a: propValue, ...remaining } = obj; // (A)
.equal(propValue, 1);
assert.deepEqual(remaining, {b:2, c:3}); assert
剩餘屬性變數,例如 remaining
(A 行),會指定一個物件,其中包含所有資料屬性,其鍵未在模式中提及。
remaining
也可以視為從 obj
非破壞性移除屬性 a
的結果。
如果我們在指定中進行物件解構,我們會遇到由 語法歧義 造成的陷阱 - 你不能以大括號開始陳述式,因為這樣 JavaScript 會認為你正在開始一個區塊
let prop;
.throws(
assert=> eval("{prop} = { prop: 'hello' };"),
()
{name: 'SyntaxError',
message: "Unexpected token '='",
; })
為什麼是
eval()
?
eval()
會延後解析(因此也會延後 SyntaxError
),直到執行 assert.throws()
的回呼函式。如果我們沒有使用它,我們會在解析此程式碼時收到錯誤,而 assert.throws()
甚至不會執行。
解決方法是將整個指定放在括號中
let prop;
= { prop: 'hello' });
({prop} .equal(prop, 'hello'); assert
陣列解構 讓你透過看起來像陣列文字的模式,批次萃取陣列元素的值
const [x, y] = ['a', 'b'];
.equal(x, 'a');
assert.equal(y, 'b'); assert
你可以透過在陣列模式中提到空洞來略過元素
const [, x, y] = ['a', 'b', 'c']; // (A)
.equal(x, 'b');
assert.equal(y, 'c'); assert
A 行中陣列模式的第一個元素是一個空洞,這就是為什麼會忽略索引為 0 的陣列元素。
陣列解構可以套用在任何可迭代的值,不只陣列
// Sets are iterable
const mySet = new Set().add('a').add('b').add('c');
const [first, second] = mySet;
.equal(first, 'a');
assert.equal(second, 'b');
assert
// Strings are iterable
const [a, b] = 'xyz';
.equal(a, 'x');
assert.equal(b, 'y'); assert
在陣列文字中,你可以有展開元素。在陣列模式中,你可以有剩餘元素(必須放在最後)
const [x, y, ...remaining] = ['a', 'b', 'c', 'd']; // (A)
.equal(x, 'a');
assert.equal(y, 'b');
assert.deepEqual(remaining, ['c', 'd']); assert
剩餘元素變數,例如 remaining
(A 行),會指定一個陣列,其中包含解構值中尚未提及的所有元素。
你可以使用陣列解構來交換兩個變數的值,而不需要暫時變數
let x = 'a';
let y = 'b';
,y] = [y,x]; // swap
[x
.equal(x, 'b');
assert.equal(y, 'a'); assert
當運算傳回陣列時,陣列解構會很有用,例如正規表示法方法 .exec()
// Skip the element at index 0 (the whole match):
const [, year, month, day] =
/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/
.exec('2999-12-31');
.equal(year, '2999');
assert.equal(month, '12');
assert.equal(day, '31'); assert
如果函式傳回多個值,解構會非常有用,這些值可以封裝為陣列或物件。
考慮一個在陣列中尋找元素的函式 findElement()
findElement(array, (value, index) => «boolean expression»)
它的第二個參數是一個函式,它接收元素的值和索引,並傳回一個布林值,表示這是否是呼叫者正在尋找的元素。
我們現在面臨一個兩難:findElement()
應該傳回它找到的元素的值還是索引?一個解決方案是建立兩個獨立的函式,但這將導致重複的程式碼,因為這兩個函式會非常相似。
以下實作透過傳回包含找到的元素索引和值的物件來避免重複
function findElement(arr, predicate) {
for (let index=0; index < arr.length; index++) {
const value = arr[index];
if (predicate(value)) {
// We found something:
return { value, index };
}
}// We didn’t find anything:
return { value: undefined, index: -1 };
}
解構有助於我們處理 findElement()
的結果
const arr = [7, 8, 6];
const {value, index} = findElement(arr, x => x % 2 === 0);
.equal(value, 8);
assert.equal(index, 1); assert
由於我們處理的是屬性金鑰,因此我們提到 value
和 index
的順序並不重要
const {index, value} = findElement(arr, x => x % 2 === 0);
關鍵在於,如果我們只對兩個結果之一感興趣,解構也能很好地為我們服務
const arr = [7, 8, 6];
const {value} = findElement(arr, x => x % 2 === 0);
.equal(value, 8);
assert
const {index} = findElement(arr, x => x % 2 === 0);
.equal(index, 1); assert
所有這些便利性結合在一起,使得這種處理多個傳回值的方式非常靈活。
如果模式的一部分沒有相符的項目,會發生什麼事?這與使用非批次運算子時發生的事情相同:你會得到 undefined
。
如果物件模式中的屬性在右側沒有相符的項目,你會得到 undefined
const {prop: p} = {};
.equal(p, undefined); assert
如果陣列模式中的元素在右側沒有相符的項目,你會得到 undefined
const [x] = [];
.equal(x, undefined); assert
undefined
和 null
進行物件解構只有當要解構的值是 undefined
或 null
時,物件解構才會失敗。也就是說,只要透過點運算子存取屬性也會失敗時,它就會失敗。
> const {prop} = undefinedTypeError: Cannot destructure property 'prop' of 'undefined'
as it is undefined.
> const {prop} = nullTypeError: Cannot destructure property 'prop' of 'null'
as it is null.
陣列解構要求解構的值是可迭代的。因此,你無法對 undefined
和 null
進行陣列解構。但是你也不能對不可迭代的物件進行陣列解構
> const [x] = {}TypeError: {} is not iterable
測驗:基礎
請參閱 測驗應用程式。
其餘章節都是進階的。
通常,如果模式沒有匹配,對應的變數會設為 undefined
const {prop: p} = {};
.equal(p, undefined); assert
如果你想要使用不同的值,你需要指定一個 預設值(透過 =
)
const {prop: p = 123} = {}; // (A)
.equal(p, 123); assert
在 A 行,我們指定 p
的預設值為 123
。會使用該預設值,因為我們解構的資料沒有名為 prop
的屬性。
這裡,我們有兩個預設值,指定給變數 x
和 y
,因為對應的元素不存在於解構的陣列中。
const [x=1, y=2] = [];
.equal(x, 1);
assert.equal(y, 2); assert
陣列模式第一個元素的預設值為 1
;第二個元素的預設值為 2
。
你也可以為物件解構指定預設值
const {first: f='', last: l=''} = {};
.equal(f, '');
assert.equal(l, ''); assert
解構的物件中既沒有屬性鍵 first
,也沒有屬性鍵 last
。因此,會使用預設值。
使用屬性值簡寫,這段程式碼會變得更簡單
const {first='', last=''} = {};
.equal(first, '');
assert.equal(last, ''); assert
考慮我們在本章學到的內容,參數定義與陣列模式有很多共同點(剩餘元素、預設值等)。事實上,以下兩個函式宣告是等效的
function f1(«pattern1», «pattern2») {
// ···
}
function f2(...args) {
const [«pattern1», «pattern2»] = args;
// ···
}
到目前為止,我們只在解構模式中使用變數作為 指定目標(資料接收器)。但你也可以使用模式作為指定目標,這使你能夠將模式巢狀到任意深度
const arr = [
first: 'Jane', last: 'Bond' },
{ first: 'Lars', last: 'Croft' },
{ ;
]const [, {first}] = arr; // (A)
.equal(first, 'Lars'); assert
在 A 行的陣列模式中,在索引 1 有巢狀的物件模式。
巢狀模式可能很難理解,因此最好適度使用。
測驗:進階
請參閱 測驗應用程式。