ES6學習(二)之解構賦值及其原理
1、基本語法
1.1、陣列
// 基礎型別解構 let [a, b, c] = [1, 2, 3] console.log(a, b, c) // 1, 2, 3 // 物件陣列解構 let [a, b, c] = [{name: '1'}, {name: '2'}, {name: '3'}] console.log(a, b, c) // {name: '1'}, {name: '2'}, {name: '3'} // ...解構 let [head, ...tail] = [1, 2, 3, 4] console.log(head, tail) // 1, [2, 3, 4] // 巢狀解構 let [a, [b], d] = [1, [2, 3], 4] console.log(a, b, d) // 1, 2, 4 // 解構不成功為undefined let [a, b, c] = [1] console.log(a, b, c) // 1, undefined, undefined // 解構預設賦值 let [x = 1] = [undefined];// x=1; let [x = 1] = [null];// x=null; // 陣列成員嚴格等於undefined,預設值才會生效 let [x = 1, y = x] = [];// x=1; y=1 let [x = 1, y = x] = [2];// x=2; y=2 let [x = 1, y = x] = [1, 2]; // x=1; y=2 let [x = y, y = 1] = [];// ReferenceError: y is not defined 因為x用y做預設值時,y還沒有宣告
1.2 物件
// 物件屬性解構 let { f1, f2 } = { f1: 'test1', f2: 'test2' } console.log(f1, f2) // test1, test2 // 可以不按照順序,這是陣列解構和物件解構的區別之一 let { f2, f1 } = { f1: 'test1', f2: 'test2' } console.log(f1, f2) // test1, test2 // 解構物件重新命名 let { f1: rename, f2 } = { f1: 'test1', f2: 'test2' } console.log(rename, f2) // test1, test2 // 巢狀解構 let { f1: {f11}} = { f1: { f11: 'test11', f12: 'test12' } } console.log(f11) // test11 // 預設值 let { f1 = 'test1', f2: rename = 'test2' } = { f1: 'current1', f2: 'current2'} console.log(f1, rename) // current1, current2
1.3 字串/數值/布林值
// String let [ a, b, c, ...rest ] = 'test123' console.log(a, b, c, rest) // t, e, s, [ 't', '1', '2', '3' ] let {length : len} = 'hello'; // en // 5 // number let {toString: s} = 123; s === Number.prototype.toString // true // boolean let {toString: s} = true; s === Boolean.prototype.toString // true // Map let [a, b] = new Map().set('f1', 'test1').set('f2', 'test2') console.log(a, b) // [ 'f1', 'test1' ], [ 'f2', 'test2' ] // Set let [a, b] = new Set([1, 2, 3]) console.log(a, b) // 1, 2
2、使用場景
2.1、 淺拷貝
let colors = [ "red", "green", "blue" ]; let [ ...clonedColors ] = colors; console.log(clonedColors);// "[red,green,blue]"
注意這裡是淺拷貝
2.2、 交換變數
let x = 1; let y = 2; [x, y] = [y, x];
2.3、遍歷Map結構
var map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key + " is " + value); }
2.4、函式引數
function add(a,b,{c,d}){ // ... } add(1,2,{}); add(1,2)//Uncaught TypeError: Cannot destructure property `c` of 'undefined' or 'null'
解構賦值的規則是,若等號右邊的值不是物件或者陣列,就會先將其轉化成物件。由於undefined和null無法轉化成物件,所以對其進行解構賦值時會報錯。
它實際上是這樣執行的:
function add(a, b, options) { let { c,d } = options; // ... }
由於add(1,2)
沒有傳options
導致異常,如果我們options是選填的那麼可以像下面這樣
function add(a,b,{c,d}={}){ // ... }
當然大部分情況我們可以給預設值
function add ({x:0,y:0}={}){} move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, 0] move({}); // [0, 0] move(); // [0, 0]
function add ({x,y}={x:0,y:0}){} move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]
上面程式碼是為函式add
的引數指定預設值,而不是為變數x和y指定預設值,所以會得到與前一種寫法不同的結果。
undefined
就會觸發函式引數的預設值。
[1, undefined, 3].map((x = 'yes') => x);
3、基本原理
解構是ES6
提供的語法糖,其實內在是針對可迭代物件的Iterator
介面,通過遍歷器按順序獲取對應的值進行賦值。這裡需要提前懂得ES6
的兩個概念:
- Iterator
- 可迭代物件
3.1、Iterator概念
Iterato
r是一種介面,為各種不一樣的資料解構提供統一的訪問機制。任何資料解構只要有Iterator
介面,就能通過遍歷操作,依次按順序處理資料結構內所有成員。ES6中的for of的語法相當於遍歷器,會在遍歷資料結構時,自動尋找Iterator
介面。
Iterator作用:
- 為各種資料解構提供統一的訪問介面
- 使得資料解構能按次序排列處理
- 可以使用ES6最新命令 for of進行遍歷
function makeIterator(array) { var nextIndex = 0 return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++]} : {done: true} } }; } var it = makeIterator([0, 1, 2]) console.log(it.next().value) // 0 console.log(it.next().value) // 1 console.log(it.next().value) // 2
3.2、可迭代物件
可迭代物件是Iterator
介面的實現。這是ECMAScript 2015
的補充,它不是內建或語法,而僅僅是協議。任何遵循該協議點物件都能成為可迭代物件。可迭代物件得有兩個協議:可迭代協議
和迭代器協議
。
可迭代協議
:物件必須實現@@iterator
方法。即物件或其原型鏈上必須有一個名叫Symbol.iterator
的屬性。該屬性的值為無參函式,函式返回迭代器協議。
屬性 | 值 |
---|---|
Symbol.iterator
|
返回一個物件的無參函式,被返回物件符合迭代器協議。 |
迭代器協議
:定義了標準的方式來產生一個有限或無限序列值。其要求必須實現一個next()
方法,該方法返回物件有done(boolean)
和value
屬性。
屬性 | 值 |
---|---|
next
|
返回一個物件的無參函式,被返回物件擁有兩個屬性:done
和value
done
:如果迭代器已經經過了被迭代序列時為 true。這時 value 可能描述了該迭代器的返回值。如果迭代器可以產生序列中的下一個值,則為 false。這等效於連同 done 屬性也不指定。value
:迭代器返回的任何 JavaScript 值。done 為 true 時可省略。 |
通過以上可知,自定義資料結構,只要擁有Iterator
介面,並將其部署到自己的Symbol.iterator
屬性上,就可以成為可迭代物件,能被for of
迴圈遍歷。
3.3、解構語法糖
String
、Array
、Map
、Set
等原生資料結構都是可迭代物件,可以通過for of
迴圈遍歷它。故可以通過ES6解構語法糖依次獲取對應的值。
// String let str = 'test' let iterFun = str[Symbol.iterator] let iterator = str[Symbol.iterator]() let first = iterator.next() // 等效於 let [first] = 'test' console.log(iterFun, iterator, first) // 列印 // [Function: [Symbol.iterator]], {}, { value: 't', done: false } // Array let arr = ['a', 'b', 'c']; let iter = arr[Symbol.iterator](); // 以下等效於 let [first, second, third, four] = ['a', 'b', 'c'] let first = iter.next() // { value: 'a', done: false } let second = iter.next() // { value: 'b', done: false } let third = iter.next() // { value: 'c', done: false } let four = iter.next() // { value: undefined, done: true }
4、效能
到我們有解構賦值的形式來做函式引數時,執行的時候會增加很多中間變數,記憶體也會比之前高。但是業務程式碼還是更加關注可讀性和可維護性。如果你寫的是庫程式碼,可以嘗試這種優化,把引數展開後直接傳遞,到底能帶來多少效能收益還得看最終的基準測試。