ES6 迭代協議

山頂的男人發表於2019-01-15

迭代協議

可迭代協議(The iterable protocol) 和 迭代器協議(The iterator protocol)是對 ECMAScript 2015 的補充,不是新的內建或語法,僅僅是協議。可以被任何遵循某些約定的物件來實現。

可迭代協議

可迭代協議允許 JavaScript 物件去定義或者定製它們的迭代行為。

一些內建型別是可迭代,例如 Array,TypeArray,Map,Set,String,在 for…of 結構中可以迴圈遍歷值,而 Object 就不是。

為了變成可迭代物件,一個物件必須實現 @@iterator 方法, 意思是這個物件(或者它原型鏈 prototype chain 上的某個物件)必須有一個名字是 Symbol.iterator 的屬性:

Property Value
[Symbol.iterator] 無參函式,該函式返回一個物件,該物件符合迭代器協議

迭代器協議

迭代器協議定義了一種標準的方式來產生有限或無限序列的值

當一個物件實現了 next() 方法,並且符合如下定義,則該物件就是一個迭代器物件。

屬性
next 一個無參函式,返回一個物件,該物件擁有兩個屬性 done 和 value

done(boolean)

  • 如果迭代器已經到了迭代序列的末尾,done 為 false
  • 如果迭代器還可以產生序列下一個值,done 為 true

value

迭代器返回的任何 JavaScript 值,當 done 為 true,可忽略。

next 方法必須是返回包含 done 和 value 屬性的物件,如果非物件(類如 false, undefined)返回,將會丟擲 TypeError

例子

可迭代協議例子

自定義一個可迭代物件
const iterableObj = {}
iterableObj[Symbol.iterator] = function* () {
	yield 1
	yield 2
	yield 3
}
console.log([...iterableObj])
複製程式碼
接受可迭代物件的內建 APIs

許多 APIs 接受可迭代物件作為引數,例如 Map([iterable]),WeakMap([iterable]),Set([iterable]),WeakSet([iterable])

const myObj = {}
new Map([[1, `a`], [2, `b`], [3, `c`]]).get(2)               // "b"
new WeakMap([[{}, `a`], [myObj, `b`], [{}, `c`]]).get(myObj) // "b"
new Set([1, 2, 3]).has(3)                                    // true
new Set(`123`).has(`2`)                                      // true
new WeakSet(function* () {
    yield {}
    yield myObj
    yield {}
}()).has(myObj)     										 // true
複製程式碼
用於可迭代物件的語法

一些語句或者表示式可用於可迭代物件,例如 for…of 迴圈,spread operator,yield*****,destructuring assignment。

// for...of
for (let i of [1, 2, 4]) {
	console.log(i)
	// 1
	// 2
	// 4
}

// spread operator
console.log([...`abc`]) // ["a", "b", "c"]

// yield*
function* gen() {
	yield* [`a`, `b`, `c`]
}
console.log(gen().next()) // {value: "a", done: false}

// destructuring assignment
[a, b] = new Set([`a`, `b`])
console.log(a) // a
複製程式碼

迭代器協議例子

簡單迭代器
function makeIterator(array) {
	let nextIndex = 0
	return {
		next() {
			return nextIndex < array.length ? { value: array[nextIndex++], done: false } : { done: true }
		}
	}
}

const it = makeIterator([`a`, `b`])

console.log(it.next()) // {value: "a", done: false}
console.log(it.next()) // {value: "a", done: false}
console.log(it.next()) // {done: true}
複製程式碼
生成器
function* makeSimpleGenerator(arr) {
	let nextIndex = 0
	while (nextIndex < array.length) {
		yield array[nextIndex++]
	}
}

const it = makeSimpleGenerator([`a`, `b`])

console.log(it.next()) // {value: "a", done: false}
console.log(it.next()) // {value: "a", done: false}
console.log(it.next()) // {value: undefined, done: true}
複製程式碼
ES6 class
class SimpleIterator {
	constructor(data) {
		this.data = data
		this.index = 0
	}

	[Symbol.iterator]() {
		return {
			next: () => {
				return this.index < this.data.length ? { value: this.data[this.index++], done: false } : { done: true }
			}
		}
	}
}

const simple = new SimpleIterator([1, 3, 9])

for (let i of simple){
	console.log(i) // 1 3 9
}
複製程式碼

再談生成器 generator

從上面的示例可以看出,generator 是比較特殊的,generator 既是一個 生成器物件,又是一個 可迭代物件!

const generatorObj = function* () {
	yield 1
	yield 2
	yield 3
}()

console.log(typeof generatorObj.next) // function

console.log(typeof generatorObj[Symbol.iterator]) // function

console.log(generatorObj[Symbol.iterator]() === generatorObj) // true
複製程式碼

相關文章