天天看點

解構指派

es6允許按照一定模式,從數組和對象中提取值,對變量進行指派,這被稱為解構(destructuring)。

以前,為變量指派,隻能直接指定值。

es6允許寫成下面這樣。

上面代碼表示,可以從數組中提取值,按照對應位置,對變量指派。

本質上,這種寫法屬于“模式比對”,隻要等号兩邊的模式相同,左邊的變量就會被賦予對應的值。下面是一些使用嵌套數組進行解構的例子。

如果解構不成功,變量的值就等于<code>undefined</code>。

以上兩種情況都屬于解構不成功,<code>foo</code>的值都會等于<code>undefined</code>。

另一種情況是不完全解構,即等号左邊的模式,隻比對一部分的等号右邊的數組。這種情況下,解構依然可以成功。

上面兩個例子,都屬于不完全解構,但是可以成功。

如果等号的右邊不是數組(或者嚴格地說,不是可周遊的結構,參見《iterator》一章),那麼将會報錯。

上面的表達式都會報錯,因為等号右邊的值,要麼轉為對象以後不具備iterator接口(前五個表達式),要麼本身就不具備iterator接口(最後一個表達式)。

解構指派不僅适用于var指令,也适用于let和const指令。

對于set結構,也可以使用數組的解構指派。

事實上,隻要某種資料結構具有iterator接口,都可以采用數組形式的解構指派。

上面代碼中,<code>fibs</code>是一個generator函數,原生具有iterator接口。解構指派會依次從這個接口擷取值。

解構指派允許指定預設值。

注意,es6内部使用嚴格相等運算符(<code>===</code>),判斷一個位置是否有值。是以,如果一個數組成員不嚴格等于<code>undefined</code>,預設值是不會生效的。

上面代碼中,如果一個數組成員是<code>null</code>,預設值就不會生效,因為<code>null</code>不嚴格等于<code>undefined</code>。

如果預設值是一個表達式,那麼這個表達式是惰性求值的,即隻有在用到的時候,才會求值。

上面代碼中,因為<code>x</code>能取到值,是以函數<code>f</code>根本不會執行。上面的代碼其實等價于下面的代碼。

預設值可以引用解構指派的其他變量,但該變量必須已經聲明。

上面最後一個表達式之是以會報錯,是因為<code>x</code>用到預設值<code>y</code>時,<code>y</code>還沒有聲明。

解構不僅可以用于數組,還可以用于對象。

對象的解構與數組有一個重要的不同。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正确的值。

上面代碼的第一個例子,等号左邊的兩個變量的次序,與等号右邊兩個同名屬性的次序不一緻,但是對取值完全沒有影響。第二個例子的變量沒有對應的同名屬性,導緻取不到值,最後等于<code>undefined</code>。

如果變量名與屬性名不一緻,必須寫成下面這樣。

這實際上說明,對象的解構指派是下面形式的簡寫(參見《對象的擴充》一章)。

也就是說,對象的解構指派的内部機制,是先找到同名屬性,然後再賦給對應的變量。真正被指派的是後者,而不是前者。

上面代碼中,真正被指派的是變量<code>baz</code>,而不是模式<code>foo</code>。

注意,采用這種寫法時,變量的聲明和指派是一體的。對于<code>let</code>和<code>const</code>來說,變量不能重新聲明,是以一旦指派的變量以前聲明過,就會報錯。

上面代碼中,解構指派的變量都會重新聲明,是以報錯了。不過,因為<code>var</code>指令允許重新聲明,是以這個錯誤隻會在使用<code>let</code>和<code>const</code>指令時出現。如果沒有第二個<code>let</code>指令,上面的代碼就不會報錯。

上面代碼中,<code>let</code>指令下面一行的圓括号是必須的,否則會報錯。因為解析器會将起首的大括号,了解成一個代碼塊,而不是指派語句。

和數組一樣,解構也可以用于嵌套結構的對象。

注意,這時<code>p</code>是模式,不是變量,是以不會被指派。

上面代碼中,隻有<code>line</code>是變量,<code>loc</code>和<code>start</code>都是模式,不會被指派。

下面是嵌套指派的例子。

對象的解構也可以指定預設值。

預設值生效的條件是,對象的屬性值嚴格等于<code>undefined</code>。

上面代碼中,如果<code>x</code>屬性等于<code>null</code>,就不嚴格相等于<code>undefined</code>,導緻預設值不會生效。

如果解構失敗,變量的值等于<code>undefined</code>。

如果解構模式是嵌套的對象,而且子對象所在的父屬性不存在,那麼将會報錯。

上面代碼中,等号左邊對象的<code>foo</code>屬性,對應一個子對象。該子對象的<code>bar</code>屬性,解構時會報錯。原因很簡單,因為<code>foo</code>這時等于<code>undefined</code>,再取子屬性就會報錯,請看下面的代碼。

如果要将一個已經聲明的變量用于解構指派,必須非常小心。

上面代碼的寫法會報錯,因為javascript引擎會将<code>{x}</code>了解成一個代碼塊,進而發生文法錯誤。隻有不将大括号寫在行首,避免javascript将其解釋為代碼塊,才能解決這個問題。

上面代碼将整個解構指派語句,放在一個圓括号裡面,就可以正确執行。關于圓括号與解構指派的關系,參見下文。

解構指派允許,等号左邊的模式之中,不放置任何變量名。是以,可以寫出非常古怪的指派表達式。

上面的表達式雖然毫無意義,但是文法是合法的,可以執行。

對象的解構指派,可以很友善地将現有對象的方法,指派到某個變量。

上面代碼将<code>math</code>對象的對數、正弦、餘弦三個方法,指派到對應的變量上,使用起來就會友善很多。

由于數組本質是特殊的對象,是以可以對數組進行對象屬性的解構。

上面代碼對數組進行對象結構。數組<code>arr</code>的<code>0</code>鍵對應的值是<code>1</code>,<code>[arr.length - 1]</code>就是<code>2</code>鍵,對應的值是<code>3</code>。方括号這種寫法,屬于“屬性名表達式”,參見《對象的擴充》一章。

字元串也可以解構指派。這是因為此時,字元串被轉換成了一個類似數組的對象。

類似數組的對象都有一個<code>length</code>屬性,是以還可以對這個屬性解構指派。

解構指派時,如果等号右邊是數值和布爾值,則會先轉為對象。

上面代碼中,數值和布爾值的包裝對象都有<code>tostring</code>屬性,是以變量<code>s</code>都能取到值。

解構指派的規則是,隻要等号右邊的值不是對象,就先将其轉為對象。由于<code>undefined</code>和<code>null</code>無法轉為對象,是以對它們進行解構指派,都會報錯。

函數的參數也可以使用解構指派。

上面代碼中,函數<code>add</code>的參數表面上是一個數組,但在傳入參數的那一刻,數組參數就被解構成變量<code>x</code>和<code>y</code>。對于函數内部的代碼來說,它們能感受到的參數就是<code>x</code>和<code>y</code>。

下面是另一個例子。

函數參數的解構也可以使用預設值。

上面代碼中,函數<code>move</code>的參數是一個對象,通過對這個對象進行解構,得到變量<code>x</code>和<code>y</code>的值。如果解構失敗,<code>x</code>和<code>y</code>等于預設值。

注意,下面的寫法會得到不一樣的結果。

上面代碼是為函數<code>move</code>的參數指定預設值,而不是為變量<code>x</code>和<code>y</code>指定預設值,是以會得到與前一種寫法不同的結果。

<code>undefined</code>就會觸發函數參數的預設值。

解構指派雖然很友善,但是解析起來并不容易。對于編譯器來說,一個式子到底是模式,還是表達式,沒有辦法從一開始就知道,必須解析到(或解析不到)等号才能知道。

由此帶來的問題是,如果模式中出現圓括号怎麼處理。es6的規則是,隻要有可能導緻解構的歧義,就不得使用圓括号。

但是,這條規則實際上不那麼容易辨識,處理起來相當麻煩。是以,建議隻要有可能,就不要在模式中放置圓括号。

以下三種解構指派不得使用圓括号。

(1)變量聲明語句中,不能帶有圓括号。

上面三個語句都會報錯,因為它們都是變量聲明語句,模式不能使用圓括号。

(2)函數參數中,模式不能帶有圓括号。

函數參數也屬于變量聲明,是以不能帶有圓括号。

(3)指派語句中,不能将整個模式,或嵌套模式中的一層,放在圓括号之中。

上面代碼将整個模式放在圓括号之中,導緻報錯。

上面代碼将嵌套模式的一層,放在圓括号之中,導緻報錯。

可以使用圓括号的情況隻有一種:指派語句的非模式部分,可以使用圓括号。

上面三行語句都可以正确執行,因為首先它們都是指派語句,而不是聲明語句;其次它們的圓括号都不屬于模式的一部分。第一行語句中,模式是取數組的第一個成員,跟圓括号無關;第二行語句中,模式是p,而不是d;第三行語句與第一行語句的性質一緻。

變量的解構指派用途很多。

(1)交換變量的值

上面代碼交換變量<code>x</code>和<code>y</code>的值,這樣的寫法不僅簡潔,而且易讀,語義非常清晰。

(2)從函數傳回多個值

函數隻能傳回一個值,如果要傳回多個值,隻能将它們放在數組或對象裡傳回。有了解構指派,取出這些值就非常友善。

(3)函數參數的定義

解構指派可以友善地将一組參數與變量名對應起來。

(4)提取json資料

解構指派對提取json對象中的資料,尤其有用。

上面代碼可以快速提取json資料的值。

(5)函數參數的預設值

指定參數的預設值,就避免了在函數體内部再寫<code>var foo = config.foo || 'default foo';</code>這樣的語句。

(6)周遊map結構

任何部署了iterator接口的對象,都可以用<code>for...of</code>循環周遊。map結構原生支援iterator接口,配合變量的解構指派,擷取鍵名和鍵值就非常友善。

如果隻想擷取鍵名,或者隻想擷取鍵值,可以寫成下面這樣。

(7)輸入子產品的指定方法

加載子產品時,往往需要指定輸入那些方法。解構指派使得輸入語句非常清晰。