前言
年都過去半個月了,我終於又重新開始更新了。雖然只是第二篇,但是我會繼續加油努力,一定不會放棄更新的。在文章中若有什麼不妥或者您有更多建議的話,歡迎和期待您給我留言,您的每一個留言都可能成為我進步的助力,十分感謝。那就廢話不多說直接開始吧。
概念
解構賦值,顧名思義,就是解開一個結構,然後從裡面拿出值用來給變數賦值賦值。所以解構賦值主要是以資料型別來劃分的。
陣列的解構賦值
var [a,b,c] = [1,2,3];
複製程式碼
上述程式碼算是最簡單的陣列解構賦值,其實也可以看做是資料的另一種展示。比如上述程式碼與下面的程式碼其實是一樣的。
var a=1,b=2,c=3;
複製程式碼
所以解構賦值最主要的作用,是可以讓我們簡化提取值的過程。
本質上,這種寫法屬於“模式匹配”,同模式下,左邊的變數就會被賦予對應位置的右邊的值。例如:
let [a,[[b],c]] = [1,[[2],3]];
a //1
b //2
c //3
複製程式碼
並且只要模式相同,即便部分位置的變數或者值為空,依舊可以進行匹配。
let [a, , b] = [1,2,3];
a //1
b //3
let [a,b,c] = [1,2];
a//1
b//2
c//undefind
複製程式碼
當解構不成功,即變數沒有得到賦值,或者直接賦值undefind
時,變數的值就會等於undefind
。
當匹配兩邊的模式相同,且長度不同時,此時的解構賦值被稱為不完全解構。雖然叫不完全解構,但是依舊算解構成功的。
let [a,b] = [1,2,3];
a //1
b //2
複製程式碼
上面一直提到一個前提情況,那就是模式相同,沒錯,這是比較需要注意的一點。當兩邊模式不同時,解構賦值是會報錯的。
let [a] = 1;
let [b] = false;
let [c] = {};
複製程式碼
在不嚴謹的情況下,我們可以說,當兩邊的資料型別不同時,解構賦值會出現報錯。
只要某種資料結構具有Iterator介面,都可以採用陣列形式的解構賦值。Iterator介面最主要的功能是可以提供遍歷命令for...of
使用,不難猜測,其實陣列解構賦值是一個將變數遍歷迴圈,然後一一進行賦值的操作。
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
複製程式碼
以上是阮一峰大神ES6入門裡面的例項,由於個人目前還不清楚具體哪些資料結構具有Iterator
介面,所以這裡直接搬運一下。
預設值
let [a=1] = [];
a//1
複製程式碼
解構賦值操作時,可以設定一個預設值,若解構賦值操作室,對應位置上的值為undefind
時,將會給變數賦值預設值。
需要注意的是,ES6內部使用嚴格相等運算子(===)來判斷一個位置是否有值,我一般習慣稱它為全等符號。所以,與一般的判斷不同,這裡只有用於賦值的陣列成員的值為
undefined
(嚴格等於undefined
)時,預設值才會生效。
let [a=1] = [undefined];
a//1
let [b=1] = [null];
b//null
let [c=1] = [NaN];
c//NaN
複製程式碼
使用預設值時,還可以引用解構賦值的其他變數,但前提是該變數已宣告。
let [a=1,b=a] = [];
a//1
b//1
let [a=b,b=1] = [];
//ReferenceError: b is not defined
var [a=b,b=1] = [];
a//undefined
b//1
複製程式碼
物件的解構賦值
let { a,b } = { a:'1',b:'2'};
a //'1'
b //'2'
複製程式碼
物件的解構賦值與陣列的解構賦值最大的不同之處,在於陣列的解構賦值,變數的取值是由位置決定的;而物件的解構賦值,變數的取值是由屬性名來決定的,只有變數與屬性名相同,才可以取到值。
let { a , b } = { b : '2' , a : '1' };
a //1
b //2
let { a } = { b: '1' , c: '2' };
a//undefined
複製程式碼
當變數名與屬性名不一致,卻又需要進行解構賦值時,可以使用變數再進行一次解構賦值。
let obj = { a : '1' , b : '2' };
let { a : c , b : d } = obj;
c //'1'
d //'2'
複製程式碼
並且,在這過程中,實際被賦值的,其實是c
和d
。而a
和b
是模式,起到類似於一箇中介作用,不會被實際賦值。
let { a : b } = { a : '1'};
a //ReferenceError: a is not defined
b //'1'
複製程式碼
物件的解構賦值,與陣列的解構賦值一樣,也可以用於巢狀結構的物件。
let a = {
b : [
'1',
{ c : '2' }
]
};
let {
b : [
x ,
{ c }
]
} = a;
x // '1'
c // '2'
b // ReferenceError: b is not defined
複製程式碼
此時b
只是模式,所以無法被賦值。
預設值
物件的解構賦值也有預設值,預設值的設定方式與陣列相同,而不是依舊使用物件的內部寫法。
let { a = 1} = {};
a // 1
let { b : 1 } = {};
//SyntaxError: Invalid destructuring assignment target
複製程式碼
預設值生效的條件與陣列的解構賦值相同,屬性值必須嚴格等於undefined
才會生效。
let { a = 1 } = { a : undefined };
a //1
let { b = 1 } = { b : null };
b // null
let { c = 1 } = { c : NaN };
c // NaN
複製程式碼
在對巢狀的物件使用解構賦值時,需要注意,若子物件所在的父屬性不存在時,會報錯。這也是我在工作中,發現比較常見的一種報錯,還是需要多多注意的。特別是在使用多層結構的時候,例如res.data.id
。
let { a: {b} } = { c : '1' };
TypeError: Cannot destructure property `b` of 'undefined' or 'null'
複製程式碼
在使用物件解構賦值的時候,如果要對已經宣告的變數進行解構賦值,需要小心。
let a;
{a} = {a:1};
//SyntaxError: Unexpected token =
複製程式碼
這裡是因為JavaScript引擎會將{a}
當做一個程式碼塊,從而引發語法錯誤。所以需要避免將大括號寫在行首。
let a;
({a} = {a:1});
a // 1
複製程式碼
由於陣列的本質是特殊的物件,因此可以對陣列進行物件屬性的解構賦值。
let a = [1, 2, 3];
let {0 : b, 2: c} = a;
b // 1
c // 3
複製程式碼
第二行程式碼的0和2代表的是陣列的位置,可以簡單理解為以下程式碼:
let a = [1,2,3];
let b = a[0];
let c = a[2];
複製程式碼
字串的解構賦值
字串也可以進行解構賦值,因為此時字串被轉換成了一個類似陣列的物件。
let [a, b, c, d, e] = 'hello';
a // 'h'
b // 'e'
c // 'l'
d // 'l'
e // 'o'
複製程式碼
看到這裡是不是感覺這個過程有點眼熟,其實這個過程可以理解為以下程式碼:
let x = 'hello';
a = x[0];
b = x[1];
c = x[2];
...
複製程式碼
數值和布林值的解構賦值
解構賦值時,如果等號右邊是數值或者布林值,則會先轉化成物件。
let {toString:a} = 123;
a === Number.prototype.toString // true
let {toString:a} = 123;
a === Boolean.prototype.toString // true
複製程式碼
解構賦值的規則是,若等號右邊的值不是物件或者陣列,就會先將其轉化成物件。由於
undefined
和null
無法轉化成物件,所以對其進行解構賦值時會報錯。
let { a:b } = undefined;
//TypeError: Cannot destructure property `a` of 'undefined' or 'null'
let { a:b } = null;
//TypeError: Cannot destructure property `a` of 'undefined' or 'null'
複製程式碼
函式引數的解構賦值
function add({x,y]){
return x+y;
}
add({1,2}); //3
複製程式碼
函式add
的參數列面上為一個陣列,但是在傳入引數的那一刻,陣列引數就被解構成了2個變數,x
和y
。
函式引數的解構也可以用預設值。
function move({x=0,y=0} = {} ) {
return [x,y];
}
move({x:1}); // [1,0];
move({}); // [0,0];
複製程式碼
函式引數的解構有另一種寫法,會得出另一種結果。
function move({x,y} = {x:0,y:0} ) {
return [x,y];
}
move({x:1}); // [1,undefined];
move({}); // [undefined,undefined];
move(); // [0,0]
複製程式碼
上述的程式碼時為move
函式引數設定預設值,而不是為解構後的x
和y
設定預設值,所以會得出不一樣的結果。
圓括號
在使用解構賦值的時候,圓括號是否使用,是一個問題。
ES6的規則中說明,只要有可能導致解構歧義的,就不能使用圓括號。
但由於該規則的標準不容易衡量和辨別,所以一般是儘量不使用圓括號。
不能使用圓括號的情況
-
變數宣告語句
let [(a)] = [1]; let {x: (c)} = {}; //上述兩句程式碼顯示為undefined let ({x: c}) = {}; let {(x: c)} = {}; let {(x): c} = {}; let { o: ({ p: p }) } = { o: { p: 2 } }; //上述四句程式碼會報錯。 複製程式碼
上述程式碼發生這種情況,主要是因為它們都是變數宣告語句,模式不能使用圓括號。
-
函式引數 函式引數也屬於變數宣告,因此不能帶圓括號。
function a( [ ( b ) ] ) { return c; } 複製程式碼
-
賦值語句的模式
( { a:b } ) = { a:1 }; [ ({a:b}) , { c:d } ] = [{},{}]; 複製程式碼
無論是將整個模式放入圓括號中,還是將部分模式放入圓括號中,都會導致報錯。
解構賦值的用途
-
交換變數的值
let a = 1; let b = 2; [a,b] = [b,a] 複製程式碼
-
從函式返回多個值
通過解構賦值,可以很方便的從陣列或者物件裡獲取多個返回值。
function arr(){ return [1,2,3]; } let [a,b,c] = arr(); //返回一個陣列 function arr() { return { a:1, b:2 }; } let { a,b } = arr(); 複製程式碼
-
函式引數的定義 解構賦值可以方便地將一組引數與變數名對應起來。
// 引數是一組有次序的值 function f([a, b, c]) { ... }f([1, 2, 3]); // 引數是一組無次序的值 function f({a, b, c}) { ... }f({z: 3, y: 2, x: 1}); 複製程式碼
-
提取JSON資料 在提取JSON物件中的資料時,解構賦值能起到非常簡便和快速的作用,使得程式碼更加簡潔。
let jsonData = { id: 42, status: "OK", data: [867, 5309]}; let { id, status, data: number } = jsonData; console.log(id, status, number); 複製程式碼
-
函式引數的預設值 通過使用解構賦值,在給函式引數賦予預設值時,整個程式碼會顯得更加簡潔。
jQuery.ajax = function (url, { async = true, beforeSend = function () {}, cache = true, complete = function () {}, crossDomain = false, global = true, // 在這裡設定預設值 } = {}) { // 這裡則是賦值的內容,若為undefined,則使用預設值 }; 複製程式碼
-
遍歷Map結構 上文說過,面對擁有
Iterator
介面的物件時,可以使用解構賦值。在這裡,我們可以通過解構賦值快速的獲取鍵名和鍵值。const map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key + " is " + value);} // first is hello // second is world 複製程式碼
-
輸入模組的指定方法 載入模組時,需要指定輸入哪些方法。解構賦值使得輸入語句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map"); 複製程式碼
總結
在使用解構賦值的時候,整體感覺上其實就是一個遍歷過程的簡化。個人感覺最大的作用是可以將類似邏輯的程式碼進行過程簡化,從而給程式碼瘦身。
同時在其中也發現了原文章中的部分細節錯誤。例如不能使用圓括號的情況中的第一點,示例程式碼中的前兩行程式碼並沒有報錯,而是顯示undefined
。
然後這裡給自己留一個小作業,是在和朋友聊上述細節錯誤時發現的一個問題:為什麼let [(a)] = [1];
顯示undefined
,而用[(a)] = [1]
則會顯示[1]
?