迭代器与可迭代对象
迭代器(iterator),使用户在容器对象(container,例如链表或数组,虽然JS中没有数组的概念,但是基本上所有编程语言都有迭代器)上遍历的对象,使用该接口无需关心对象的内部实现细节。
从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。
在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):
在JavaScript中这个标准就是一个特定的next方法:
- 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象。
- done属性(布尔类型)
-
- 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
- 如果迭代器已将序列迭代完毕,则为 true。这种情况下, value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
- value属性
-
- 迭代器返回的任何 JavaScript 值。 done 为 true 时可省略。
下面就是一个符合条件的迭代器:
JavaScript
<script>
const names = ["abc", "cba", "nba"]
const nums = [100, 24, 55, 66, 86]
// 封装一个函数
function createArrayIterator(arr) {
let index = 0
return {
next: function() {
if (index < arr.length) {
return { done: false, value: arr[index++] }
} else {
return { done: true }
}
}
}
}
const namesIterator = createArrayIterator(names)
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
</script>
但是很显然,上述代码迭代器和数组本身是分离的,不可能每次想要遍历一个容器的时候就自己编写一个迭代函数然后调用。因此需要一个办法将容器对象和迭代器结合起来。 JavaScript使用如下方式实现:
JavaScript
<script>
// 将infos变成一个可迭代对象
const infos = {
friends: ["kobe", "james", "curry"],
[Symbol.iterator]: function() {
let index = 0
const infosIterator = {
next: function() {
// done: Boolean
// value: 具体值/undefined
if (index < infos.friends.length) {
return { done: false, value: infos.friends[index++] }
} else {
return { done: true }
}
}
}
return infosIterator
}
}
// 可迭对象可以进行for of操作
for (const item of infos) {
console.log(item)
}
// 可迭代对象必然有一个[Symbol.iterator]函数
// 数组是一个可迭代对象
const students = ["张三", "李四", "王五"]
console.log(students[Symbol.iterator])
const studentIterator = students[Symbol.iterator]()
console.log(studentIterator.next())
console.log(studentIterator.next())
console.log(studentIterator.next())
console.log(studentIterator.next())
</script>
首先容器对象必须要包含一个特定的函数: [Symbol.iterator],这是一个计算属性名(Computed Property Name)。这种语法允许你在对象字面量中使用表达式或者计算来定义属性名。这个函数名是固定的,其次这个函数需要返回一个迭代器(这个迭代器用于迭代当前的对象)。
这个时候infos就是一个可迭代对象了,可迭代对象可以执行的操作有很多,就包括for of操作,而这是一般对象无法实现的。
JS中的数组对象默认就包含了这个函数,我们因而可以直接使用相应的方法。
可迭代对象的优化处理
上面的代码中我们做if条件判断的时候仍然需要使用容器对象本身的名字if (index < infos.friends.length)
。
这意味这对象名发生改变,判断条件也需要更改,这是个基本的问题,很显然需要优化一下,并且自然的想到使用this优化,但是如果直接改成this,得到的是迭代器本身,迭代器内部没有friends数组,因此需要到上层作用域找,我们可以把函数声明改成箭头函数,箭头函数是不存在this的,箭头函数的外层是一个迭代器对象,对象本身不构成作用域,因此this就自然指向了最外层的infos作用域。
优化代码如下:
JavaScript
const infos = {
friends: ["kobe", "james", "curry"],
[Symbol.iterator]: function() {
let index = 0
// 迭代器
const infosIterator = {
next: () => {
if (index < this.friends.length) {
return { done: false, value: infos.friends[index++] }
} else {
return { done: true }
}
}
}
return infosIterator
}
}
以上都是对数组对象的遍访,我们同样有办法遍访一个对象容器:
JavaScript
<script>
const infos = {
name:"shi",
age: 18,
height:180,
[Symbol.iterator]: function () {
const entries = Object.entries(this)
let index = 0
const iterator = {
next: function () {
if (index < entries.length) {
return {done: false, value: entries[index++]}
}else {
return {done: true}
}
}
}
return iterator
}
}
</script>
Object.entries(this) 是 JavaScript 中的一个内建方法,它返回一个给定对象自己的可枚举属性的键值对数组。在这个特定的上下文中,this 引用的是包含 name、age 和 height 等属性的 infos 对象。
可迭代对象的应用场景
- JavaScript中语法: for ...of、展开语法(spread syntax)、 yield*、解构赋值;
- 创建一些对象时需要传入一些可迭代对象: new Map([Iterable])、 new WeakMap([iterable])、 new Set([iterable])、 new WeakSet([iterable]);
- 一些方法的调用: Promise.all(iterable)、 Promise.race(iterable)、 Array.from(iterable);
生成器
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
生成器函数
生成器函数也是一个函数,但是和普通的函数有一些区别:
- 生成器函数需要在function的后面加一个符号: *
- 生成器函数可以通过yield关键字来控制函数的执行流程
- 生成器函数的返回值是一个Generator(生成器)
生成器事实上是一种特殊的迭代器;
当我们给一个普通函数加上*号,它的函数体是不会执行的,只会返回一个生成器对象。
function* foo( ) {console.log('1'); foo();
但是代码的执行可以被yield控制,要想执行内部的代码,先要接收返回的生成器对象,然后通过这个对象调用上面说过的next方法,当遇到yield时,会中断执行,若要继续执行,则需再次调用next方法。
我们之前学习迭代器时,知道迭代器的next是会有返回值的;
但是我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果;
JavaScript
// 定义生成器函数
function* foo(name1) {
console.log("执行内部代码:1111", name1)
console.log("执行内部代码:2222", name1)
yield "aaaa"
console.log("执行内部代码:3333", name2)
console.log("执行内部代码:4444", name2)
yield "bbbb"
console.log("执行内部代码:5555", name3)
console.log("执行内部代码:6666", name3)
yield "cccc"
return undefined
}
// 调用生成器函数, 返回一个生成器对象
const generator = foo("next1")
console.log(generator.next()) // { done: false, value: "aaaa" }
console.log(generator.next()) // { done: false, value: "bbbb" }
console.log(generator.next()) // { done: false, value: "cccc" }
console.log(generator.next()) // {done: true, value: undefined}
// 在中间位置直接return, 结果后面的代码都不会执行
console.log(generator.next()) // { done: false, value: "aaaa" }
console.log(generator.next()) // { done: true, value: "bbbb" }
console.log(generator.next()) // { done: true, value: undefined }