ES6-解構賦值及原理

weixin_34370347發表於2019-01-21

基礎語法

陣列

// 基礎型別解構
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 [a = 1, b = 2] = [3]
console.log(a, b) // 3, 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
複製程式碼

函式引數

// 引數解構
function func1({ x, y }) {
    return x + y
}
func1({ x: 1, y: 2}) // 3

function func1({ x = 1, y = 2 }) {
    return x + y
}
func1({x: 4}) // 6
複製程式碼

String/Map/Set

// String
let [ a, b, c, ...rest ] = 'test123'
console.log(a, b, c, rest) // t, e, s, [ 't', '1', '2', '3' ]

// 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
複製程式碼

解構原理

解構是ES6提供的語法糖,其實內在是針對可迭代物件Iterator介面,通過遍歷器按順序獲取對應的值進行賦值。這裡需要提前懂得ES6的兩個概念:

  • Iterator
  • 可迭代物件

Iterator概念

Iterator是一種介面,為各種不一樣的資料解構提供統一的訪問機制。任何資料解構只要有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
複製程式碼

可迭代物件

可迭代物件是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迴圈遍歷。

// 自定義可迭代物件
let obj = {
    [Symbol.iterator] : function() {
        return{
            next: function() {
                return { value: 1, done: true }
            }
        }
    }
}

for (let item of obj) {
    console.log(item) // 不會報錯,因為obj已經是可迭代物件
}
複製程式碼

解構語法糖

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 }
複製程式碼

原生object物件是預設沒有部署Iterator介面,即object不是一個可迭代物件。因為遍歷時,不知道到底哪個屬性先遍歷,哪個屬性後遍歷,需要開發者手動指定。不過object部署Iterator介面沒有必要,因為ES6提供了Map資料結構。實際上物件被解構時,會被當作Map進行解構。所以雖然Map和Object很多地方相似,但ES6引入Map、Set物件是有其原因的。

參考文章

阮一峰ECMAScript 6

Mozilla - for of

Mozilla - Iteration protocols

相關文章