Iterator的奥秘:深入理解JavaScript的迭代机制

前言

最近看到一道面试题觉得很有意思,如何才能使下面的代码打印出结果

js 复制代码
const obj = {
    a: 1,
    b: 2,
    c: 3
}
for (let v of obj) {
    console.log(v)
}

猛地看上去挺简单,但是里面其实蕴含了很多东西,想让其打印出结果就必须要了解一个概念就是迭代器Iterator,这也正是本文所要讲的,先说下答案吧。

js 复制代码
Object.prototype[Symbol.iterator]=function(){
    return Object.value(this)[Symbol.Iterator]()
}

只需要在for of之前给对象添加Symbol.iterator属性就可以完美打印出1,2,3,这个属性是一个函数,这里巧妙借用了数组可迭代这一点完成对对象的遍历,首先对象本身会默认调用Symbol.iterator方法,那么此时函数中的this指向的是对象本身,然后使用Object.value方法取出该对象的值并组成一个数组,数组本身是有Symbol.iterator方法的,所以相当于是在遍历一个数组。听不懂?没关系,接下来才是正文部分,那就先从概念部分说起吧。

Iterator是什么

相信经验比较丰富的一些同学可能会看到过这个,在一些数组或者Map、Set中经常会看到这个属性,但在对象(Object)中就没有,下面就来一起彻底弄明白这个东西。

Iterator名为遍历器,ES6中规定遍历器是一种机制,也是一种接口,为各种不同的数据结构提供统一的访问机制 ,任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。原生具备Iterator的数据结构有

  • Array
  • Map
  • Set
  • String
  • arguments对象
  • NodeList对象

为什么需要Iterator

在Javascript中表示集合的数据结构有数组、对象、Map和Set数据结构,它们都有各自的特点,但同时也有一个共同的需求:遍历它们的内容。然而不同的数据结构都有对应的遍历方法,比如,数组有forEach、map、filter等方法,对象有for in/Object.keys()方法,Map和Set也都有各自的遍历方法,这就导致一个问题,如果要处理一个不同类型的集合,那么就要写很多的判断条件来根据集合的类型选择对应的遍历方法,这显然是不方便的。

于是ES6中为了解决这个问题引入了Iterator这种统一的访问机制,即for of循环,使用for of循环时会自动去寻找该数据结构是否部署了Iterator接口。这就是为什么会出现Iterator的原因,总结一下:

  1. 为各种数据结构,提供一个统一的、简便的访问接口;
  2. 使得数据结构的成员能够按某种次序排列(自定义遍历行为);
  3. for of使用;

Iterator的实现

Iterator其实就是一个对象,对象中有一个next方法(固定格式),第一次调用对象的next方法可以返回当前数据结构中的第一个成员,第二次调用对象的next方法可以返回当前数据结构中的第二个成员,直到数据结构中的结束位置结束。下面来模拟实现一个遍历器生成函数:

js 复制代码
const arr = [1,2,3];
function makeIterator(arr){
    let nextIndex = 0;
    return {
        next(){
               return nextIndex < arr.length?
               {value:arr[nextIndex++],done:false}:
               {value:undefined,done:true}
        }
    }
}

const iterator = makeIterator(arr);
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 3, done: false }
console.log(iterator.next()) // { value: undefined, done: true }

可以看出调用遍历器对象的next方法,就可以遍历事先给定的数据结构。在makeIterator函数中首先声明了一个表示从头开始的下标,每调用一次next方法就会以对象的形式返回数据结构中对应的元素,其中value表示数据结构中当前位置的值,done表示遍历是否结束。

上面提到的Symbol.iterator属性,它本身是一个函数,是当前数据结构默认的遍历器生成函数,这个函数的实现类似于makeItrerator函数,它默认部署在可遍历的数据结构中。

只要数据结构具有Symbol.iterator属性就可以使用for of遍历,for...of循环内部调用的其实就是数据结构的Symbol.iterator方法。

js 复制代码
const arr = [1,2,3,4];
for (let v of arr){
    console.log(v) // 1 2 3 4 
}

Object对象上的没有默认的Iterator接口,是因为对象是无序的,里面的哪个属性先遍历哪个属性后遍历是不确定的,需要开发者手动确定。要使其可以进行遍历,添加一个遍历器生成函数即可。

js 复制代码
const obj = {
    name:"xxx",
    age:18,
}
// 如果不加此函数,将会报错 obj is not iterable
Object.prototype[Symbol.iterator] = function(){
    let nextIndex = 0
    return {
        next:()=>{ // 确定this指向
            return nextIndex < Object.values(this).length?{value:Object.values(this)[nextIndex++],done:false}:{value:undefined,done:true}
        }
    }
}
for (let v of obj){
    console.log("v",v) // xxx 18
}

此外其他默认调用Iterator接口的场景还有解构赋值、扩展运算符。再补充一下for in for of是两种不同的数据结构,分别用于遍历两种不同的数据结构,for in 枚举的对象的键,而不是值,for of直接遍历的是对象的值,而不是键。

总结

这篇文章主要讲解了JavaScript中一个可迭代对象是如何进行迭代的,详细说明了Iterator遍历器的概念、出现的原因以及如何实现,属于原理级别的知识,虽然在工作中不会经常用到,但是本着知其然知其所以然的道理了解它的原理,内部是如何运行的才能知道一些报错信息出现的原因以及解决方案,大家加油吧!

相关推荐
小菜yh6 分钟前
后端人需知
java·前端·javascript·vue.js·设计模式
周万宁.FoBJ10 分钟前
vue3 实现文本内容超过N行折叠并显示“...展开”组件
开发语言·前端·javascript
w2sfot11 分钟前
JS加密=JS混淆?(JS加密、JS混淆,是一回事吗?)
javascript·js加密·js混淆
Jiaberrr29 分钟前
uniapp视频禁止用户推拽进度条并保留进度条显示的解决方法——方案二
前端·javascript·uni-app·音视频
python资深爱好者33 分钟前
VB中如何实现设计模式(如单例模式、工厂模式等)
javascript·单例模式·设计模式
人工智能的苟富贵1 小时前
全面解析 iOS 和 Android 内嵌 H5 页面通信与交互实现方案
android·javascript·ios·交互
森叶1 小时前
webpack 的打包target讲解 & node环境打包下的文件存储造成不易察觉的坑点
前端·webpack·node.js
新智元1 小时前
陶哲轩全网悬赏「最强大脑」!AI + 人类颠覆数学难题?凡尔赛网友已下场
前端·人工智能
亿牛云爬虫专家1 小时前
Puppeteer的高级用法:如何在Node.js中实现复杂的Web Scraping
前端·javascript·爬虫·node.js·爬虫代理·puppeteer·代理ip
uhan251 小时前
2024年9月26日 linux笔记
linux·服务器·前端