迭代器模式
Iterator(迭代器)
要成为可迭代对象,必须要实现@iterator方法,这个方法可以通过Symbol.iterator属性(可能在原型对象上)访问到。
因此可以通过有没有Symbol.iterator属性来判断是能不能进行遍历。
| 值 | 属性 |
|---|---|
| Symbol.itarator | 一个无参数的函数,返回一个满足迭代器协议的可迭代对象(遍历器) |
下面这些语法要使用可迭代对象
- for...of..循环
- 展开语法(数组、函数参数)
- yield *
- 结构赋值
// 以yield* 为例
let generator = function* () {
yield 1;
yield* obj; // obj是个可迭代对象,obj为代码块3定义的
yield 5;
};
var iterator = generator();
iterator.next(); // {value: 1, done: false}
iterator.next(); // {value: 'yxfan', done: false}
iterator.next(); // {value: 18, done: false}
iterator.next(); // {value: 5, done: false}
下面这些数据结构内置可迭代对象,他们的原型对象都实现了@iterator方法
-
String
-
Array
-
Map
-
Set
-
函数的arguments对象
-
NodeList (document.querySelectorAll)
-
HTMLCollection (document.getElementBy....)
-
const arr = ['a', 'b', 'c']; // 1 const iterator = arr[Symbol.iterator](); // 2 iterator.next(); // { value: 'a', done: false } iterator.next(); // { value: 'b', done: false } iterator.next(); // { value: 'c', done: false } iterator.next(); // { value: undefined, done: true }由于Array原生就具备遍历器接口,所以在2处可以通过
Symbol.iterator属性访问到生成遍历器的函数,并调用,得到遍历器iterator因为对象没有内置遍历器接口,因此要用for...of在对象上,就得手动实现Symbol.iteratorconst obj = { name: 'yxfan', age: 18 }; obj[Symbol.iterator] = function() { const keys = Object.keys(obj); let index = 0; // 老必包了 return { next: function() { const object = { value: obj[keys[index]], done: index < keys.length ? false : true } index++; return object; } } } const iterator = obj[Symbol.iterator](); console.log(iterator.next()); // {value: "yxfan", done: false} console.log(iterator.next()); // {value: 18, done: false} console.log(iterator.next()); // {value: 18, done: true} for(let o of obj) { console.log(o); // 依次打印 yxfan 18 }上面这个例子中,手动的给
obj对象设置了Symbol.iterator属性,其值为一个生成器函数。这样我们就可以像for..of..数组一样,去for...of...对象啦!Generator(生成器)
与Iterator接口的关系
任意一个对象的
[Symbol.iterator]方法,等于该对象的遍历器生成函数,调用生成函数,会返回一个遍历器对象。而
generator函数就是遍历器生成函数,因此可以把generator函数赋值给[Symbol.iterator]来改造上一个例子:
const obj = { name: 'yxfan', age: 18, * [Symbol.iterator]() { let index = 0; const keys = Object.keys(this); for(let k of keys) { yield [k, this[k]]; } } }; const iterator = obj[Symbol.iterator](); console.log(iterator.next()); // {value: ['name', 'yxfan'], done: false} console.log(iterator.next()); // {value: ['age', 18], done: false} console.log(iterator.next()); // {value: undefined, done: true} for(let [key, value] of obj) { console.log(key, value); // name yxfan age 18 }利用
generator我们不用在[Symbol.iterator]中部署next方法直接用yield给出每一步的返回值即可generator方法会返回一个遍历器,遍历器的next方法运行逻辑如下- 遇到
yield表达式,就暂停执行后面的操作,并将紧跟在yield后面表达式的值,作为返回对象的value - 下一次调用
next方法时,继续往下执行,直到遇到下一个yield表达式 - 如果没有遇到新的
yield表达式值,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值 - 如果该函数没有
return语句,则返回的对象的value属性值为undefined。
generator函数不会立即执行function * g() { console.log('我不会立即执行喔'); } const iterator = g(); setTimeout(() => { console.log(iterator.next()); }, 2000);如果
g()是个普通函数,一经调用则会马上输出log,但是这个是个generator函数,调用时会返回遍历器对象,我尝试着打印一下iterator - 遇到
只有调用next时函数g才会执行
总结
该篇主要是说迭代器模式,因此Iterator和generator只是稍微提了一下,跟详实的可看ES6
by the way,迭代器模式在实际开发中用的并不多,但还是得知道是怎么实现的(方便面试)