## 迭代器 ### 概念 **迭代器**(Iterator)是 JavaScript 中一种特殊的对象,它提供了一种**统一的、通用的**方式**遍历**个各种不同类型的数据结构。可以遍历的数据结构包括:数组、字符串、Set、Map 等**可迭代对象**。我们也可以自定义实现迭代器,以支持遍历自定义的数据结构。 通过迭代器,我们可以按顺序逐个获取数据中的元素,不需要手动跟踪索引(索引也可称之为指针、游标)。迭代器的行为很像数据库中的游标(cursor)。 我们也不需要关心可迭代对象内部的实现细节,即不需要关心目标对象是数组还是字符串,还是其他的数据结构。对于迭代器来说,这些数据结构都是一样的处理方式。 迭代器是一种常见的编程模式,最早出现在1974年设计的CLU编程语言中。不仅仅在JS中,其他许多编程语言(比如 Java、Python 等)都提供了迭代器的概念和实现。技术实现各有不同,但目的都是帮助我们用通用的方式遍历对象的数据结构,提高代码的简洁性、可读性和维护性。 ### 迭代协议 迭代协议并不是编程语言的内置实现或语法,而是协议。迭代协议具体分为两个协议:可迭代协议、迭代器协议。 **迭代器协议**规定了产生一系列值(无论是有限个还是无限个)的标准方式。 迭代器是一个具体的对象,这个对象要符合迭代器协议。**在JS中,某个对象只有实现了符合特定要求的 next() 方法,这个对象才能成为迭代器**。 ### 实现原理:next() 方法 在JS中,迭代器的实现原理是通过定义一个特定的`next()` 方法,该方法在每次迭代中返回一个包含两个属性的对象:done 和 value。 具体来说,next() 方法有如下要求: (1)参数:无参数或者有一个参数。 (2)返回值:返回一个应当有以下两个属性的对象。属性值如下: - done 属性(Boolean 类型):表示迭代是否已经完成。当迭代器遍历完所有元素时,done 为 true,否则为 false。具体解释如下: - 如果迭代器可以产生序列中的下一个值,则为 false,这等价于没有指定 done 属性。 - 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 可以省略,如果 value 依然存在,即为迭代结束之后的默认返回值。 - value 属性:包含当前迭代步骤的值,可能是具体的值,也可能是 undefined。每次调用 next() 方法时,迭代器返回下一个值。done 为true时,可以省略。 ### 举例:为数组创建迭代器 按照上面讲的迭代器协议,我们可以给一个数组手动创建一个用于遍历的迭代器。代码举例如下: ```js const strArr = ['qian', 'gu', 'yi', 'hao']; // 为数组封装迭代器 function createArrayIterator(arr) { let index = 0; return { next: () => { if (index < arr.length) { return { done: false, value: arr[index++] }; } else { return { done: true }; } }, }; } const strArrIterator = createArrayIterator(strArr); console.log(JSON.stringify(strArrIterator.next())); console.log(JSON.stringify(strArrIterator.next())); console.log(JSON.stringify(strArrIterator.next())); console.log(JSON.stringify(strArrIterator.next())); console.log(JSON.stringify(strArrIterator.next())); ``` 打印结果: ``` {"done":false,"value":"qian"} {"done":false,"value":"gu"} {"done":false,"value":"yi"} {"done":false,"value":"hao"} {"done":true} ``` 你可能会有疑问:实际开发中,我们真的需要大费周章地为一个简单的数组写一个迭代器函数吗?数组直接拿来遍历不就完事了吗? 是的,这大可不必。初衷是为了了解迭代器的原理。 ## 可迭代对象 > 我们要注意区分一些概念:迭代器、可迭代对象、容器。迭代器是提供迭代功能的对象。可迭代对象是被迭代的目标对象,也称之为容器。 当我们使用迭代器的方式去遍历数组、字符串、Set、Map 等数据结构时,这些数据对象就属于可迭代对象。这些数据对象本身,内部就自带了迭代器。 可是,有些数据对象,并不具备可迭代的能力,那要怎么封装成可迭代对象呢?以及,可迭代对象需要具备什么特征?可迭代对象有什么用处?这就是本段要讲的内容。 ### 为普通对象创建迭代器 代码举例: ```js const myObj1 = { strArr: ['qian', 'gu', 'yi', 'hao'], }; // 为 myObj.strArr 封装迭代器 let index = 0; const strArrIterator = { next: () => { if (index < myObj1.strArr.length) { return { done: false, value: myObj1.strArr[index++] }; } else { return { done: true }; } }, }; console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); ``` 打印结果: ``` {done: false, value: 'qian'} {done: false, value: 'gu'} {done: false, value: 'yi'} {done: false, value: 'hao'} {done: true} ``` ### 将普通对象封装为可迭代对象 上面的数据 myObj1,不属于可迭代对象,因此我们单独写了一个迭代器 strArrIterator 对象,这两个对象时分开的。 还有一种更高级的做法是,把迭代器封装到数据对象的内部。完事之后,这个数据对象就是妥妥的可迭代对象。 将普通的数据对象封装为可迭代对象时,**具体做法**是:在数据对象内部,创建一个名为`[Symbol.iterator]`的迭代器函数,这个函数名是固定的(这种写法属于计算属性名);然后这个函数内需要返回一个迭代器,用于迭代当前的数据对象。 代码举例如下: ```js const myObj2 = { strArr: ['qian', 'gu', 'yi', 'hao'], // 在 myObj2 的内部创建一个迭代器 [Symbol.iterator]: function () { let index = 0; const strArrIterator = { next: function () { if (index < myObj2.strArr.length) { return { done: false, value: myObj2.strArr[index++] }; } else { return { done: true }; } }, }; return strArrIterator; }, }; // 获取 myObj2 的迭代器对象 const strArrIterator = myObj2[Symbol.iterator](); // 通过迭代器遍历 myObj2.strArr 的数据 console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); ``` 打印结果: ``` {done: false, value: 'qian'} {done: false, value: 'gu'} {done: false, value: 'yi'} {done: false, value: 'hao'} {done: true} ``` ### 可迭代对象的特征 上面的代码中,我们将 myObj2 数据对象封装成了可迭代对象。凡是可迭代对象,都具备如下特征: 1、可迭代对象都有一个 [Symbol.iterator]函数。通过这个函数,我们可以进行一些数据遍历操作。我们拿一个简单的数组进行举例: ```json const myArr = ['qian', 'gu', 'yi', 'hao']; console.log(typeof myArr[Symbol.iterator]); console.log(myArr[Symbol.iterator]); console.log(typeof myArr[Symbol.iterator]()); console.log(myArr[Symbol.iterator]()); // 获取数组的迭代器对象 const myIterator = myArr[Symbol.iterator](); // 通过迭代器的 next() 方法遍历数组 console.log(myIterator.next()); console.log(myIterator.next()); console.log(myIterator.next()); console.log(myIterator.next()); console.log(myIterator.next()); ``` image-20230525211636012 2、可迭对象可以进行 for ... of 操作。其实 for ... of 底层就是调用了 iterator 方法。代码举例: ```js const myArr = ['qian', 'gu', 'yi', 'hao']; // 可迭代对象可以进行 for ... of 操作。for ... of 也是一种遍历操作。 for (const item of myArr) { // 这里的 item,其实就是迭代器里的 value 属性的值。 console.log(item); } ``` 打印结果: ``` qian gu yi hao ```