1、解構(gòu)賦值是對賦值運算符的擴展。
2、它是一種針對數(shù)組或者對象進行模式匹配,然后對其中的變量進行賦值。
3、代碼書寫上顯得簡潔且易讀,語義更加清晰明了;而且還方便獲取復(fù)雜對象中的數(shù)據(jù)字段。
在解構(gòu)賦值操作過程中,有下面兩部分參與:
1、解構(gòu)的源:解構(gòu)賦值表達式的右邊部分。如 let a = 1;
2、解構(gòu)的目標:解構(gòu)賦值表達式的左邊部分。如 let a = 1;
解構(gòu)賦值的規(guī)則是:解構(gòu)源(等號右邊的值)不是對象或數(shù)組,則先將解構(gòu)源轉(zhuǎn)換成對象,然后再進行解構(gòu)賦值。
字符串在進行進行解構(gòu)賦值時,會先被轉(zhuǎn)換成一個類似數(shù)組的對象,然后再按數(shù)組模式進行賦值。既然被轉(zhuǎn)換成了一個類似數(shù)組的對象,那么就有 length屬性(類似數(shù)組的對象都有一個length屬性),因此還可以對length屬性進行解構(gòu)賦值。
// 字符串在進行進行解構(gòu)賦值時,會先被轉(zhuǎn)換成一個類似數(shù)組的對象。const [a, b, c, d] = 'ES6 Demo';console.log(a); // Econsole.log(b); // Sconsole.log(c); // 6console.log(d); //// 既然被轉(zhuǎn)換成了一個類似數(shù)組的對象,那么就有l(wèi)ength屬性(類似數(shù)組的對象都有一個length屬性),因此還可以對length屬性進行解構(gòu)賦值。let {length: len} = 'ES6 Demo';console.log(len); // 8
解構(gòu)源是數(shù)值或布爾值時,跟字符串的解構(gòu)賦值操作類似,先將數(shù)值或布爾值轉(zhuǎn)換成對象。
// 數(shù)值和布爾值的包裝對象都有toString屬性,所以還是能取到值。let {toString: tempDes} = 123;console.log(tempDes); // ƒ toString() { [native code] }console.log(tempDes === Number.prototype.toString); // truelet {toString: tempDestru} = true;console.log(tempDestru); // ƒ toString() { [native code] }console.log(tempDestru === Boolean.prototype.toString); // true
let {xxx, yyy} = {xxx: 123, yyy: 'aaa'};console.log(xxx); // 123console.log(yyy); // aaa// 對象的解構(gòu)賦值,由于對象內(nèi)的屬性沒有固定順序,所以變量的取值實際上由對象內(nèi)屬性的位置決定,而且變量名還必須與對象內(nèi)的屬性名相同,才能取到正確的值。// 變量名與對象內(nèi)屬性的順序可以不一致,反正對象內(nèi)屬性的順序也不固定。let {bbb, aaa, ccc} = {aaa: 7890, bbb: 'xxxx'};console.log(bbb); // xxxxconsole.log(aaa); // 7890console.log(ccc); // undefined 右側(cè)對象內(nèi)沒有ccc屬性,導(dǎo)致取值失敗,所以為undefined// 如果解構(gòu)失敗,變量的值也等于undefinedlet {ddd} = {aaa: 7890, bbb: 'xxxx'};console.log(ddd); // undefined 實際上還是因為右側(cè)對象內(nèi)沒有ddd屬性,導(dǎo)致變量ddd取值失敗,所以為undefined
let {aaa: yyy} = {aaa: 7890, bbb: 'xxxx'};console.log(yyy); // 7890let tempObj = {name: 'ES6', description: 'Demo'};let {name: alias, description: descrip} = tempObj;console.log(alias); // ES6console.log(descrip); // Demo
// 這實際上說明,對象的解構(gòu)賦值是下面形式的簡寫。對象的解構(gòu)賦值其內(nèi)部機制,是先找到同名屬性,然后再賦給對應(yīng)的變量。真正被賦值的是后者,而不是前者let {aaa: aaa, bbb: bbb} = {aaa: 'xxx', bbb: 'yyy'};// 下面代碼中,aaa、bbb只是匹配的模式,ccc、ddd才是真變量。真正被賦值的是變量ccc、ddd,而不是模式aaa、bbblet {aaa: ccc, bbb: ddd} = {aaa: 'xxx', bbb: 'yyy'};console.log(ccc); // xxxconsole.log(aaa); // Uncaught ReferenceError: aaa is not definedconsole.log(ddd); // yyyconsole.log(bbb); // Uncaught ReferenceError: bbb is not defined
let tempObj = { a: [ 'ES6', {c: 'Demo'} ]};let {a: [b, {c}]} = tempObj;console.log(a); // Uncaught ReferenceError: a is not defined 注意書寫方式,此時a是匹配模式,不是變量,因此不會被賦值。console.log(b); // ES6// 對比上下兩種寫法的差異,以及各自 a 充當(dāng)?shù)慕巧?/span>let {a, a: [b, {c}]} = tempObj;console.log(a); // (2) ["ES6", {…}] 此時的 a 是變量,才能被賦值。console.log(c); // Demo// 忽略對象中對應(yīng)屬性的數(shù)據(jù)let {a: [b, {}]} = tempObj;console.log(b); // ES6let {a: [b]} = tempObj;console.log(b); // ES6// 最后,由于可以把一個對象設(shè)置為另一個對象的原型對象(即一個對象繼承了另一個對象),所以對象的解構(gòu)賦值還可以獲取到繼承的屬性。let obj1 = {};let obj2 = {name: 'ES6'};Object.setPrototypeOf(obj1, obj2);let {name} = obj1;console.log(name); // ES6
// 對象的解構(gòu)賦值,也可以使用剩余運算符,即不確定后續(xù)還有多少數(shù)據(jù)需要解構(gòu)時,可以將剩余數(shù)據(jù)統(tǒng)一放到 剩余運算符參數(shù)內(nèi)。let {a, b, ...c} = {a: 10, b: 20, c: 30, d: 40};console.log(a); // 10console.log(b); // 20console.log(c); // {c: 30, d: 40}// 剩余運算符(...)只能放到解構(gòu)目標(解構(gòu)賦值表達式的左邊部分)的最后面,否則會報錯。let {a, ...c, b} = {a: 10, b: 20, c: 30, d: 40}; // Uncaught SyntaxError: Rest element must be last element 雖然叫剩余運算符,但其實跟rest參數(shù)一樣,都是存放不確定個數(shù)的參數(shù)或值的參數(shù)
// 對象的解構(gòu)賦值可以給變量指定默認值。// 變量 x 賦默認值,對象內(nèi)無 x 屬性let {x = 1} = {};console.log(x); // 1// 變量 y 賦默認值,對象內(nèi)有 x 屬性,無 y 屬性let {x, y = 3} = {x: 2};console.log(x); // 2console.log(y); // 3// 變量 y 賦默認值(x 是匹配模式),對象內(nèi)無 x 屬性,無 y 屬性let {x: y = 4} = {};console.log(x); // Uncaught ReferenceError: x is not definedconsole.log(y); // 4// 變量 y 賦默認值(x 是匹配模式),對象內(nèi)有 x 屬性let {x: y = 5} = {x: 6};console.log(y); // 6
對象解構(gòu)賦值 默認值生效的前提是:對象內(nèi)屬性值必須嚴格等于undefined。
// 對象內(nèi) destructuring 屬性不存在,所以 destructuring 屬性值嚴格等于undefined,默認值生效let {destructuring: tempStr = 'ES6 Test Demo'} = {};console.log(tempStr); // ES6 Test Demo// 對象內(nèi) x 屬性存在且嚴格等于undefined,默認值生效let {x = 3} = {x: undefined};console.log(x); // 3// 對象內(nèi) x 屬性存在,雖然 x 屬性值為null,但null不嚴格等于undefined,故默認值不生效let {x = 3} = {x: null};console.log(x); // null
// 如果在解構(gòu)之前聲明了一個變量,且將這個變量用于解構(gòu)賦值,則代碼在編譯時就會報錯。// 因為JS引擎將{x}理解成了一個代碼塊,從而發(fā)生語法錯誤。解決方式:用一個圓括號將解構(gòu)賦值操作括起來(不將大括號寫在行首,避免JS將其解釋為代碼塊,就解決了這個問題,和之前進行匿名函數(shù)聲明,限定參數(shù)作用域類似)。let x;{x} = {x: 1}; // Uncaught SyntaxError: Unexpected token '='// 正確寫法let x;({x} = {x: 1});console.log(x); // 1// 由于數(shù)組本質(zhì)是特殊的對象,因此可以對數(shù)組進行對象屬性的解構(gòu)。let tempArray = [1, 2, 3];let {0: first, 2: last} = tempArray;console.log(first); // 1console.log(last); // 3// 上面對數(shù)組進行對象解構(gòu)。數(shù)組tempArray 0鍵對應(yīng)值 1,2鍵對應(yīng)值 3。方括號這種寫法,屬于"屬性名表達式"
// 從數(shù)組中按序提取值,按照對應(yīng)位置,對變量進行賦值。let [a, b, c] = [1, 2, 3];console.log(a); // 1console.log(b); // 2console.log(c); // 3
// 本質(zhì)上,對象和數(shù)組的解構(gòu)賦值都屬于"模式匹配",只要等號兩邊的模式相同,左邊的變量就會被賦予對應(yīng)的值。所以對象 解構(gòu)賦值存在的功能,在數(shù)組這對應(yīng)的都存在。let [a, [b, [c]]] = [1, [2, [3]]];console.log(a); // 1console.log(b); // 2console.log(c); // 3// 忽略變量let [x, , y] = [1, 2, 3];console.log(x); // 1console.log(y); // 3let [, , third] = ["ES6", "Test", "Demo"];console.log(third); // Demo// 剩余運算符let [head, ...other] = [1, 2, 3, 4];console.log(head); // 1console.log(other); // (3) [2, 3, 4]// 如果解構(gòu)失敗,變量的值就等于undefinedlet [x, y, ...z] = ['123'];console.log(x); // 123console.log(y); // undefined 右側(cè)數(shù)組內(nèi)只有 0 位置有數(shù)據(jù),1位置及以后位置地址不存在,也不存在數(shù)據(jù)。因而解構(gòu)失敗,無法取到正確的值,所以為undefinedconsole.log(z); // [] 剩余運算符(取變量匹配剩下的數(shù)據(jù)),只不過在這是空數(shù)組。變量匹配賦值尚且不夠,哪還有剩余運算符的。let [a] = [];console.log(a); // undefinedlet [b, c] = [1];console.log(b); // 1console.log(c); // undefined
// 如果解構(gòu)的源不是數(shù)組(或嚴格地說,不是可遍歷結(jié)構(gòu)),則編譯時會報錯。let [aaa] = 1; // Uncaught TypeError: 1 is not iterablelet [aaa] = false; // Uncaught TypeError: false is not iterablelet [aaa] = NaN; // Uncaught TypeError: NaN is not iterablelet [aaa] = undefined; // Uncaught TypeError: undefined is not iterablelet [aaa] = null; // Uncaught TypeError: null is not iterablelet [aaa] = {}; // Uncaught TypeError: {} is not iterable// 以上各語句,解構(gòu)的源要么轉(zhuǎn)為對象以后不具備 Iterator接口(前五個表達式),要么本身就不具備 Iterator接口(最后一個表達式)。
// 事實上,只要一個數(shù)據(jù)結(jié)構(gòu)具有 Iterator接口,則該數(shù)據(jù)結(jié)構(gòu)就可以采用數(shù)組形式的解構(gòu)賦值。function* fibs() { let a = 0, b = 1; while (true) { yield a; [a, b] = [b, a + b]; }}let [first, second, third, fourth, fifth, sixth] = fibs();console.log(sixth); // 5
// 和對象的解構(gòu)賦值一樣,數(shù)組的解構(gòu)賦值也允許給變量指定默認值。// 同樣,數(shù)組的解構(gòu)賦值 默認值生效也有前提:即數(shù)組內(nèi)對應(yīng)位置的成員值必須嚴格等于undefined。let [a = true] = [];console.log(a); // truelet [b, c = '123'] = ['456'];console.log(b); // 456console.log(c); // 123let [x, y = 'Demo'] = ['ES6', undefined];console.log(x); // ES6console.log(y); // Demolet [z = 1] = [null];console.log(z); // null null不嚴格等于undefined
// 如果默認值是一個表達式,則該表達式表現(xiàn)為惰性求值,即只在調(diào)用時,才會求值,其余時候就是一個表達式。function testFun() { console.log('aaa'); // return 123;}let [x = testFun()] = [1];console.log(x); // 1 因為 x 能正常取到值,所以函數(shù)testFun根本不會執(zhí)行let [y = testFun()] = [];console.log(y); // undefined y 無法取到值,所以函數(shù)testFun執(zhí)行了。但由于函數(shù)testFun并未返回值,所以 y 還是undefined
// 還可以引用解構(gòu)賦值的其他變量作為默認值,但該變量必須已經(jīng)聲明。let [x = 1, y = x] = []; // x=1; y=1let [x = 1, y = x] = [2]; // x=2; y=2let [x = 1, y = x] = [1, 2]; // x=1; y=2let [x = y, y = 1] = []; // Uncaught ReferenceError: Cannot access 'y' before initialization x 用 y 做默認值時,y還沒有聲明。
解構(gòu)賦值的規(guī)則是,只要解構(gòu)的源不是對象或數(shù)組,就先將解構(gòu)源轉(zhuǎn)換成對象,再進行解構(gòu)賦值。但 undefined和null 都無法轉(zhuǎn)換為對象,所以對它們進行解構(gòu)賦值,都會報錯。
let {a} = undefined; // Uncaught TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined.let {prop: x} = undefined; // Uncaught TypeError: Cannot destructure property 'prop' of 'undefined' as it is undefined.let = null; // Uncaught TypeError: Cannot destructure property 'b' of 'null' as it is null.let {prop: y} = null; // Uncaught TypeError: Cannot destructure property 'prop' of 'null' as it is null.