面试题,给出一个对象,如何封装一个类使其可遍历,就是算是手写一个Generator函数吧

1
2
3
4
5
6
var test = {
0: 'chen',
1: 'da',
2: 'peng',
length: 3
}

思考一会,小试牛刀!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var test = {
0: 'chen',
1: 'da',
2: 'peng',
length: 3
}
class MakeIterator {
constructor(obj) {
this.length = Object.keys(obj).length;
this.index = 0
this.obj = obj
}
[Symbol.iterator]() {return this}; // 这个是难点也是最重要的一点
next() {
return this.index < this.length
? {value: this.obj[this.index ++], done: false}
: {value: undefined, done: true}
}
}
for(item of new MakeIterator(test)) {
console.log(item)
}

介绍

遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就阔以完成遍历操作;

模拟next方法返回值的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var it = makeIterator(['a', 'b'])
it.next() // {value: 'a', done: false}
it.next() // {value: 'b', done: false}
it.next() // {value: undefined, done: true}
function makeIterator(array) {
var index = 0
return {
next: function() {
return index < array.length ?
{value: array[index++], done: false} :
{value: undefined, done: true}
}
}
}

默认 Iterator 接口

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for…of循环。当使用for…of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是可遍历的(iterable)。
Symbol.iterator本身是一个函数,数据结构默认的遍历器生成函数,执行这个函数就会返回一个遍历器。
原生具备 Iterator 接口的数据结构如下:

  • Array

  • Set

  • Arguments

  • Map

  • String

  • NodeList对象

  • typedArray

    对于类似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口。

    1
    2
    3
    4
    5
    var arr = new Set([1, 2, 3, 2, 3])
    let it = arr[Symbol.iterator](); // 得到遍历器对象
    it.next()
    it.next()
    it.next()

    变量arr是一个数组,原生就具有遍历器接口,部署在arr的Symbol.iterator属性上面。所以,调用这个属性,就得到遍历器对象

类似数组的对象调用数组的Symbol.iterator方法的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
var iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: [][Symbol.iterator]
}
for(var item of iterable) {
console.log(item, 'eee')
}
// a eee
// b eee
// c eee

若没有部署[].Symbol.iterator则会报错 iterable is not iterable

普通对象部署数组的Symbol.iterator方法无效

1
2
3
4
5
6
7
8
9
10
11
var iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: [][Symbol.iterator]
}
for(var item of iterable) {
console.log(item, 'eee')
}
// undefined "eee"

手动部署iterator属性的时候记得是 [Symbol.iterator]: [][Symbol.iterator] 这种写法

手写实现一个遍历器

目的: 定义一个对象,通过调用类Iterator实现可遍历

1
2
3
4
5
6
7
8
9
10
let obj = {
0: "a",
1: "b",
2: "c",
length: 3,
};
for(var i of obj) {
console.log(i)
}
// Uncaught TypeError: obj is not iterable

如何调用类Iterator实现可遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyIterator {
constructor(obj) {
this.obj = obj;
this.length = Object.keys(obj).length;
this.index = 0;
}
[Symbol.iterator]() {return this}
next() {
return this.index < this.length
? {value: this.obj[this.index ++], done: false}
: {value: undefined, done: true}
}
}
let obj = {
0: "a",
1: "b",
2: "c",
length: 3,
};
for(var i of new MyIterator(obj)) {
console.log(i)
}

场景

有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法),除了下文会介绍的for…of循环,还有其他场合

解构赋值

1
2
3
var set = new Set().add('a').add('b').add('c')
var [x, ...y] = set;
console.log(x, y) // a ['b', 'c']

扩展运算符

1
2
3
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd'] // a b c d

上面代码的扩展运算符内部就调用 Iterator 接口。
只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。

yield*

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

1
2
3
4
5
6
7
8
9
10
var ge = function* () {
yield 1;
yield [2, 3, 4];
yield 55;
}
var ite = ge();
ite.next() // {value: 1, done: false}
ite.next() // {value: [2, 3, 4], done: false}
ite.next() // {value: 55, done: false}
ite.next() // {value: undefined, done: true}

思考 如何让第二个next里的数据遍历呢?

1
2
3
4
5
6
var ge = function* () {
yield 1;
yield* [2, 3, 4];
yield 55;
}
var ite = ge();

其他场景

由于数组的遍历回调用遍历器接口,所以任何接受数组作为参数的场景都会调用遍历器

  • for…of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
  • Promise.all()
  • Promise.race()

字符串的 Iterator 接口

字符串是一个类似数组的对象,也原生具有 Iterator 接口

1
2
3
4
5
6
7
8
9
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"

var iterator = someString[Symbol.iterator]();

iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }

上面代码中 调用Symbol.iterator方法返回一个遍历器对象,在这个遍历器上可以调用 next 方法,实现对于字符串的遍历

Iterator与Generator

Symbol.iterator()方法的最简单实现, 两者关系

任意一个对象的Symbol.iterator方法 === 该对象的遍历器生成函数Generator,调用该函数会生成该对象的遍历器对象.
Generator函数就是遍历器生成函数,因此能把Generator赋值给对象的Symbol.iterator属性,从而使该对象具有iterator接口

  • 第一种
    1
    2
    3
    4
    5
    6
    7
    8
    var it = {
    [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
    }
    }
    console.log([...it]) // [1, 2, 3]
  • 第二种
    1
    2
    3
    4
    5
    6
    7
    8
    var it2 = {
    * [Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
    }
    }
    console.log(...it2) // 1 2 3

for…of 循环

一个结构只要部署了Symbol.iterator属性,就被视为具体Iterator接口,就能用for of 循环遍历成员,也就是for of 循环内部调用的是数据结构的Symbol.iterator属性

for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、Generator 对象,以及字符串

思考

1.所有的模拟数据结构,用到的Symbol.iterator ,再用的时候都需要加上方括号,不管是

1
2
3
4
5
6
7
var it2 = {
* [Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}

2.面试的时候如何用大白话说清楚,Iterator跟Generator俩者之间的关系

参考链接