[TOC] #### 1. 认识迭代器 --- 迭代器(iterator): 使用户在容器对象(数组/对象/链表)上遍访的对象,使用该接口无需关心对象的内部实现细节 在 JavaScript 中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol) + 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式 + 在 JavaScript 中这个标准就是一个特定的 next 方法 next 方法有以下要求: + 它是一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象 + done(boolean): + 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性) + 如果迭代器已将序列迭代完毕,则为 true。这种情况下 value 是可以选的,如果它依然存在,即为迭代结束之后的默认返回值 + value:迭代器返回的任何 JavaScript 值。done 为 true 时可以省略 ```javascript const list = ['html', 'css', 'js'] // 按照 next 方法的规范要求,我们给数组 list 创建一个简单的迭代器 let index = 0 const listIterator = { next() { if (index < list.length) { return { done: false, value: list[index++] } } else { return { done: true, value: undefined } } } } const value1 = listIterator.next() const value2 = listIterator.next() const value3 = listIterator.next() const value4 = listIterator.next() console.log(value1) // { done: false, value: 'html' } console.log(value2) // { done: false, value: 'css' } console.log(value3) // { done: false, value: 'js' } console.log(value4) // { done: true, value: undefined } ``` 为了做成更通用的迭代器,应该将其封装为一个函数,多个数组都可以调用这个迭代器 ```javascript const list = ['html', 'css', 'js'] function createArrayIterator(array) { let index = 0 return { next() { if (index < array.length) { return { done: false, value: array[index++] } } else { return { done: true } } } } } const iterator = createArrayIterator(list) console.log(iterator.next()) console.log(iterator.next()) console.log(iterator.next()) console.log(iterator.next()) ``` #### 2. 可迭代对象 --- 在 JavaScript 中,可迭代对象(iterable)是指那些可以被迭代访问的对象 + 这类对象实现了 `[Symbol.iterator]` 方法,该方法返回一个迭代器对象(iterator) + 通过这个迭代器,可以依次访问对象中的每个元素 将数据转为可迭代对象有什么好处呢 ? + 当一个对象变成可迭代对象的时候,就可以进行某些迭代操作 + 比如:for...of 操作时,其实就会调用它的 `[Symbol.iterator]` 方法 对象默认是不可迭代的: ```javascript const object = { name: 'liang', age: 18 } console.log(object[Symbol.iterator]) // undefined(说明对象没有实现迭代器) // 可迭代对象才能使用 for of 遍历 for (const value of object) { } // Uncaught TypeError: object is not iterable ``` 上面我们是给数组创建迭代器,现在我们可以给对象创建一个迭代器。但这个对象和迭代器是分开的,能合在一起吗 ```javascript // 普通对象 const info = { friends: ['Tom', 'Kevin'], } // 给这个对象创建迭代器 let index = 0 const iterator = { next() { if (index < info.friends.length) { return { done: false, value: info.friends[index++] } } else { return { done: true } } } } console.log(iterator.next()) // { done: false, value: 'Tom' } console.log(iterator.next()) // { done: false, value: 'Kevin' } console.log(iterator.next()) // { done: true } ``` 将对象变为可迭代对象,并将迭代器方法放入对象内部 ```javascript // 将 info 变为可迭代对象 // 1.必须实现一个特定的函数: [Symbol.iterator], // 2.这个函数需要返回一个迭代器(用于迭代当前对象) const info = { friends: ['Tom', 'Kevin'], [Symbol.iterator]() { let index = 0 const iterator = { next() { if (index < info.friends.length) { return { done: false, value: info.friends[index++] } } else { return { done: true } } } } return iterator } } ``` 可迭代对象必然具备以下特点,能通过调用 `[Symbol.iterator]` 方法实现迭代访问 ```javascript // 可迭代对象必然有 [Symbol.iterator] 函数,数组就是一个可迭代对象 // 数组是一个可迭代对象 const list = ['html', 'css', 'js'] const iterator = list[Symbol.iterator]() console.log(iterator.next()) // { value: 'html', done: false } console.log(iterator.next()) // { value: 'css', done: false } console.log(iterator.next()) // { value: 'js', done: false } console.log(iterator.next()) // { value: undefined, done: true } ``` 可迭代对象的优化处理 ```javascript const info = { name: 'liang', age: 20, height: 180, [Symbol.iterator]() { let index = 0 const array = Object.entries(this) return { // 将 next 改为箭头函数,此时 this 就指向了 info 对象 next: () => { if (index < array.length) { return { done: false, value: array[index++] } } else { return { done: true } } } } } } for (const item of info) { let [key, value] = item console.log(key, value) } ``` 很多原生对象已经实现了可迭代协议,会生成一个可迭代对象的,这些对象都可以使用 for...of 进行遍历 + 数组、字符串、Map、Set、argument、NodeList ```javascript // 数组 const arr = ['html', 'css', 'js'] for (const item of arr) { console.log(item) } // 字符串 const string = 'hello world' for (const item of string) { console.log(item) } // Set const set = new Set(['liang', 'idea']) for (const item of set) { console.log(item) } ``` #### 3. 可迭代对象应用场景 --- 可迭代对象的应用场景 + JavaScript 中语法:for...of、展开语法(spread syntax)、yield\*、解构赋值 + 创建一些对象时:new Map(iterable)、new Set(iterable)、newWeakMap(iterable) + 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable) 使用展开语法将对象作为参数传入到函数中,默认会报错 ```javascript const obj = { name: 'liang', age: 18 } function foo(...arg) { console.log(arg); } // Uncaught TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function foo(...obj) ``` 将对象变为可迭代对象之后,就可以使用展开语法传参了 ```javascript const obj = { name: 'liang', age: 18, [Symbol.iterator]() { let index = 0 const array = Object.entries(this) return { next: () => { if (index < array.length) { return { done: false, value: array[index++] } } else { return { done: true } } } } } } function foo(...arg) { console.log(arg); } foo(...obj) ``` #### 4. 自定义类的对象迭代 --- 类的可迭代协议其实和对象一样,也是只需要实现 `[Symbol.iterator]` 方法即可 ```javascript class Person { constructor(name, age, jobs) { this.name = name this.age = age this.jobs = jobs } // 自定义类的迭代器 [Symbol.iterator]() { let index = 0 return { next: () => { if (index < this.jobs.length) { return { done: false, value: this.jobs[index++] } } else { return { done: true } } } } } } const why = new Person('why', 20, ['html', 'css']) for (const item of why) { console.log(item) } ``` #### 5. 迭代器的中断 --- 迭代器在某些情况下会在没有完全迭代的情况中断,我们可以监听到这个中断: + 比如遍历的过程中通过 break、return、throw 中断了循环操作 + 比如在解构的时候,没有解构所有的值 ```javascript class Person { constructor(jobs) { this.jobs = jobs } [Symbol.iterator]() { let index = 0 return { next: () => { if (index < this.jobs.length) { return { done: false, value: this.jobs[index++] } } else { return { done: true } } }, // 监听到迭代器中断 return: () => { console.log('监听到迭代器中断了'); // 这里也需要返回一个对象,否则会报错 // Uncaught TypeError: Iterator result undefined is not an object return { done: true } } } } } const why = new Person(['html', 'css']) for (const item of why) { console.log(item) if (item === 'html') { break; } } ``` #### 6. 生成器函数的基本使用 --- 生成器是 ES6 中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行 + 平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常 生成器函数也是一个函数,但是和普通的函数有一些区别,生成器事实上是一种特殊的迭代器: + 首先,生成器函数需要在 function 的后面加一个符号:`*` + 其次,生成器函数可以通过 yield 关键字来控制函数的执行流程 + 最后,生成器函数的返回值是一个生成器对象(Generator) ```javascript // 1. 生成器函数定义:function 后面跟上 * // 2. 生成器函数的返回值是一个生成器对象 // 3. 要执行生成器函数内部的代码,需要调用生成器对象的 next() 方法 // 4. 当遇到 yield 时,就会中断执行,再次 next() 方法会继续执行 function* foo() { console.log(111) console.log(222) yield console.log(333) yield console.log(444) } const generator = foo() // 函数中的执行体不会执行,它只是返回了一个生成器对象 generator.next() // 第一次调用,输出 111 222 generator.next() // 第二次调用,输出 333 generator.next() // 第三次调用,输出 444 ``` #### 7. 生成器对象的 next() 返回值 --- 前面已经说过,生成器是一种特殊的迭代器,那么生成器对象的 `next()` 方法的返回值是什么呢 ? ```javascript function* foo() { console.log('执行内部代码: 111') yield // 相当于 yield -> done: false console.log('执行内部代码: 222') yield // 相当于 yield -> done: false console.log('执行内部代码: 333') // 后续没有代码 相当于 return -> done: true } // 可以发现 next 的返回值确实是迭代器的返回值格式 const generator = foo() console.log(generator.next()) // { value: undefined, done: false } console.log(generator.next()) // { value: undefined, done: false } console.log(generator.next()) // { value: undefined, done: true } ``` 其实 `next()` 方法返回值中的 value 由 `yield` 决定 ```javascript function* foo() { console.log('执行内部代码: 111') yield 'aaa' console.log('执行内部代码: 222') yield 'bbb' console.log('执行内部代码: 333') } const generator = foo() console.log(generator.next()) // { value: 'aaa', done: false } console.log(generator.next()) // { value: 'bbb', done: false } console.log(generator.next()) // { value: undefined, done: true } ``` 过程中如果遇到 `return`,就代表迭代完了,直接返回 `done: true` ```javascript function* foo() { console.log('执行内部代码: 111') yield 'aaa' console.log('执行内部代码: 222') return 'bbb' console.log('执行内部代码: 333') // 这里的代码不会执行了,因为上面已经 return } const generator = foo() console.log(generator.next()) // { value: 'aaa', done: false } console.log(generator.next()) // { value: 'bbb', done: true } console.log(generator.next()) // { value: undefined, done: true } ``` #### 8. 生成器对象的 next() 参数 --- 当调用 `next()` 方法并传入参数时,该参数会作为上一次 `yield` 表达式的返回值 ```javascript function* foo(one) { // 第一次调用 next() console.log('执行内部代码: ' + one) // 执行内部代码: hello const two = yield 'aaa' // 第二次调用 next() console.log('执行内部代码: ' + two) // 执行内部代码: 第二次调用 next const three = yield 'bbb' // 第三次调用 next() console.log('执行内部代码: ' + three) // 执行内部代码: 第三次调用 next } const generator = foo('hello') console.log(generator.next()) // 第一次调用 next 是不传参数的,但是可以在 foo(参数) 传递 console.log(generator.next('第二次调用 next')) console.log(generator.next('第三次调用 next')) ``` #### 9. 生成器的提前结束 --- 生成器对象的`return()` 方法用于终止生成器,并返回一个对象,其 `value` 属性为传入的参数 ```javascript function* foo() { console.log('执行内部代码: 111') yield '111' console.log('执行内部代码: 222') yield '222' console.log('执行内部代码: 333') } const generator = foo() console.log(generator.next()) // { value: '111', done: false } // 终止生成器 console.log(generator.return()) // { value: undefined, done: true } console.log(generator.next()) // { value: undefined, done: true } ``` 生成器对象的`throw()` 方法用于向生成器函数内部抛出一个错误(异常),并且恢复生成器的执行 ```javascript function* foo() { console.log('执行内部代码: 111') yield '111' console.log('执行内部代码: 222') yield '222' console.log('执行内部代码: 333') } const generator = foo() console.log(generator.next()) // { value: '111', done: false } console.log(generator.throw(new Error('next2 error'))) // 抛出异常:Uncaught Error: next2 error console.log(generator.next()) ``` 可以在生成器内部捕获该异常后,这样外部就没有异常提示了 ```javascript function* foo() { console.log('执行内部代码: 111') try { yield '111' } catch (error) { console.log(error) // Error: next2 error } console.log('执行内部代码: 222') yield '222' console.log('执行内部代码: 333') } const generator = foo() console.log(generator.next()) // { value: '111', done: false } console.log(generator.throw(new Error('next2 error'))) // { value: '222', done: false } console.log(generator.next()) // { value: undefined, done: true } ``` #### 10. 生成器替代迭代器 --- 这是上面我们给数组创建的迭代器,我们可以使用生成器替代迭代器,简化代码 ```javascript const list = ['html', 'css', 'js'] function createArrayIterator(array) { let index = 0 return { next() { if (index < array.length) { return { done: false, value: array[index++] } } else { return { done: true } } } } } const iterator = createArrayIterator(list) console.log(iterator.next()) console.log(iterator.next()) console.log(iterator.next()) ``` 使用生成器改造后(代码更加简洁、直观): ```javascript const list = ['html', 'css', 'js'] function* createArrayIterator(array) { // 第一次改造:依次取出数组元素 // yield array[0] // yield array[1] // yield array[2] // 第二次改造:格局数组长度,依次取出元素 for (let index = 0; index < array.length; index++) { yield array[index] } } const iterator = createArrayIterator(list) console.log(iterator.next()) console.log(iterator.next()) console.log(iterator.next()) ``` 事实上我们还可以使用 `yield*` 来生产一个可迭代对象: + 这个时候相当于是一种 yield 的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值 ```javascript const list = ['html', 'css', 'js'] function* createArrayIterator(array) { yield* array } const iterator = createArrayIterator(list) console.log(iterator.next()) console.log(iterator.next()) console.log(iterator.next()) ``` #### 11. 生成器自定义类的可迭代对象 --- 前面我们给类做对象迭代时,代码是这样的: ```javascript const info = { friends: ['Tom', 'Kevin'], [Symbol.iterator]() { let index = 0 const iterator = { next: () => { if (index < this.friends.length) { return { done: false, value: this.friends[index++] } } else { return { done: true } } } } return iterator } } const iterator = info[Symbol.iterator]() console.log(iterator.next()) console.log(iterator.next()) ``` 对象的迭代器同样可以使用 `yield*` 来简化代码: ```javascript const info = { friends: ['Tom', 'Kevin'], // 因为 yield* 要放在生成器函数中,所以要将 [Symbol.iterator] 变为生成器函数 // 在类中需要将*放在[Symbol.iterator]前面,[Symbol.iterator]* 时会报错 *[Symbol.iterator]() { // 1. 使用 yield* 简化代码 yield* this.friends } } const iterator = info[Symbol.iterator]() console.log(iterator.next()) console.log(iterator.next()) for (const item of info) { console.log(item); } ``` 再次优化对象的迭代器,使其更加通用 ```javascript const info = { name: 'liang', age: 20, height: 180, friends: ['Tom', 'Kevin'], *[Symbol.iterator]() { yield* Object.entries(this) } } for (const item of info) { const [key, value] = item console.log({ key, value }); } ```