ES6:【深扒】 JavaScript 中的迭代器

简介: ES6:【深扒】 JavaScript 中的迭代器

image.png

image.png

发现问题

image.png

我们先来看几个例子

image.png

这是一个简单的数组,如果要获取它的每一项数据,我们可以采用 for 循环,当然也可以采用 forEach 循环,这样很酷

image.png

image.png\

当纯这样还没什么问题

我们再看下面的例子。将给定字符串单个字符输出

image.png

image.png

可以采用 for 循环和 for...in 循环

问题就这样出现了

上面两个例子中我们的目的都只是遍历,但是却需要去考虑采用不同的遍历方式

在第一段代码中我们遍历的是一个数组,第二段遍历的是一个字符串,我们采用了不同的方法,也就是说我们在面对不同数据结构时往往会采取不同的遍历方式。

在 JavaScript 中原有的表示“集合”的数据结构,主要是 Array 和 Object ,而在 ES6中又新增了 Map和 Set 两种,同时我们还可以组合使用这些数据结构。面对如此众多的数据结构,我们却不能使用一个统一的遍历方法来获取数据,这就是所存在的问题!

当然在 ES6 中提供了一个全新的遍历方法 for...of循环,但是 for...of 有一个非常重要的地方

只能对实现了 iterator 接口的对象进行遍历取值

所以说 for...of就只是 iterator 雇佣的打工仔,也叫语法糖

image.png

Iterator 迭代器

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

1. Iterator 的作用

  1. 为各种数据结构,提供一个统一的、简便的访问接口
  2. 使得数据结构的成员能够按某种次序排列
  3. ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。

2. Iterator 的工作原理

  1. 创建一个指针对象,指向当前数据结构的起始位置
  2. 第一次调用next方法时,指针指向数据结构的第一个成员
  3. 接下来调用next方法,指针后移,直到指向最后一个成员

每次调用 next 方法都会返回一个值,这个值是一个 object,包含两个属性,valuedonevalue表示具体的返回值,done 是布尔类型的,表示集合是否遍历完成,完成则返回 true,否则返回 false

3. 手写实现 iterator 接口

image.png

image.png

我们来分析一下上面的代码,myIterator 函数是一个遍历器生成函数,它的作用是返回一个遍历器对象,也是指针对象。

我们通过 next 方法来移动指针,next 方法内部通过闭包来保存指针nextIndex的值,每次调用 next 方法 nextIndex都会 +1 ,然后根据nextIndex 的值从数组内取出数据作为 value ,通过索引判断得到 done ,当无数据可用时,超过数组最大索引,无可用数据返回,此时 done 为 true

可迭代对象

了解过了 iterator,并且我们也已经知道了如何创建一个遍历器对象,但是这和我们先前所说的 for...of 循环有什么关系呢

这里首先我们需要了解一下 for...of 的运行机制

当 for...of循环执行时,循环内部会自动调用这个对象上的迭代器方法Symbol.iterator , 依次执行迭代器对象的 next 方法,将 next 方法的返回值赋值给 for ...of 内的变量,从而得到具体的值,实现遍历。

1. 手写实现可迭代对象

一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”。

Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数,执行这个函数,就会返回一个迭代器对象。

也就是说要实现可迭代对象只要在对象上部署了Symbol.iterator属性,为它创建一个迭代器方法就可以了

image.png

显然实现了 iterator 接口,可以被 for...of 成功遍历

2. Iterator 原生应用场景

有些对象我们并没有为它们部署 Iterator 接口,但是仍然可以使用 for...of 进行遍历。这是因为在ES6中有些对象已经默认部署了这个接口。

原生具备 Iterator 接口的数据结构:

  • Array
  • set容器
  • map容器
  • String
  • 函数的 arguments 对象
  • NodeList 对象

Array

在数组上成功的找到了 Symbol.iterator 方法,并能够执行返回迭代器对象,同时验证了for...of循环成功执行

image.png

image.png

image.png

Q&A

看到这里你可能会想,为什么这么多数据结构都实现了默认部署,为什么偏偏对象没有呢?

当然是有原因的

对象可能有各种各样的属性,不像数组的值是有序的,所以对对象遍历时根本不知道如何确定先后顺序,所以需要我们手动实现

提前退出循环

普通的 for 循环是可以随时中断的,for...of 循环作为 for 和 forEach 的升级版同样是可以的

迭代器对象除了有 next 方法,还有两个可选方法 return 方法和 throw 方法

return 方法的使用场景是,当 for...of 循环提前退出,就会调用 return 方法。

需要特别注意的是,return 方法必须有一个 object 类型的返回值

我们在前面代码的基础上添加上 return 方法,并在 for...of 循环中采用 break 语句来中断循环,循环提前退出,自动调用 return 方法输出提前退出

77

let iteratorObj = {
    items: [1, 2, 'ljc'],
    // 部署Symbol.iterator属性
    [Symbol.iterator]: function () {
        let self = this
        let i = 0
        return {
            next: function () {
                // 类型转化为Boolean
                let done = (i >= self.items.length)
                // 数据确认
                let value = !done ? self.items[i++] : undefined
                // 数据返回
                return {
                    done,
                    value
                }
            },
            return () {
                console.log('提前退出');
                return {
                    done: true
                }
            }
        }
    }
}
for (let item of iteratorObj) {
    console.log(item); // 1
    break;
}

注意

如果采用抛出异常的方式退出,会先执行return 方法再抛出异常

关于 throw 方法会在下篇生成器文章中提到

Iterator 接口使用场景

除了 for...of 循环会自动调用 iterator 接口之外,还有几个场景也会自动调用

1. 解构赋值

对可迭代对象进行解构赋值时,会默认调用 Symbol.iterator 方法

image.png

由于解构赋值适用于可迭代对象,那么我们对自己自定义的可迭代对象解构赋值试试

image.png

成功的实现了解构赋值

2. 扩展运算符

扩展运算符也会默认调用Symbol.iterator方法,可以将当前数据结构转化为数组

image.png

3. yield*

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

image.png

这里并不是它的主战场,下节说明

总结

ES6 中新增了新的数据结构,为了提供一种统一的遍历方法,新增了 for...of 方式。而 for...of 执行的时候会自动调用迭代器来取值

只有实现了 Iterator 接口的对象才能采用 for...of

迭代器是一个返回迭代器对象的方法

ES6 中很多场景都采用了 Iterator ,可以多注意一下,新的东西往往是向上的

下一篇将来讲解生成器Generator

image.png

目录
相关文章
|
4月前
|
前端开发 JavaScript
JavaScript 面试系列:如何理解 ES6 中 Generator ?常用使用场景有哪些?
JavaScript 面试系列:如何理解 ES6 中 Generator ?常用使用场景有哪些?
|
2月前
|
JavaScript 前端开发 安全
ECMAScript 6(以下简称 ES6)的出现为 JavaScript 带来了许多新的特性和改进,其中 let 和 const 是两个非常重要的关键字。
ES6 引入了 `let` 和 `const` 关键字,为 JavaScript 的变量管理带来了革新。`let` 提供了块级作用域和暂存死区特性,避免变量污染,增强代码可读性和安全性;`const` 用于声明不可重新赋值的常量,但允许对象和数组的内部修改。两者在循环、函数内部及复杂项目中广泛应用,有助于实现不可变数据结构,提升代码质量。
41 5
|
2月前
|
存储 JavaScript 前端开发
JS的ES6知识点
【10月更文挑战第19天】这只是 ES6 的一些主要知识点,ES6 还带来了许多其他的特性和改进,这些特性使得 JavaScript 更加现代化和强大,为开发者提供了更多的便利和灵活性。
40 3
|
3月前
|
JavaScript 前端开发 索引
JavaScript ES6及后续版本:新增的常用特性与亮点解析
JavaScript ES6及后续版本:新增的常用特性与亮点解析
110 4
|
3月前
|
自然语言处理 JavaScript 前端开发
JavaScript高级——ES6基础入门
JavaScript高级——ES6基础入门
43 1
|
3月前
|
前端开发 JavaScript 小程序
JavaScript的ES6中Promise的使用以及个人理解
JavaScript的ES6中Promise的使用以及个人理解
40 1
|
2月前
|
前端开发 JavaScript
JavaScript新纪元:ES6+特性深度解析与实战应用
【10月更文挑战第29天】本文深入解析ES6+的核心特性,包括箭头函数、模板字符串、解构赋值、Promise、模块化和类等,结合实战应用,展示如何利用这些新特性编写更加高效和优雅的代码。
69 0
|
4月前
|
JavaScript 前端开发 Oracle
软件工程师,学习下JavaScript ES6新特性吧
软件工程师,学习下JavaScript ES6新特性吧
54 9
|
4月前
|
JavaScript 索引
|
4月前
|
前端开发 JavaScript
ES6新标准下JS异步编程Promise解读
ES6新标准下JS异步编程Promise解读
49 3