迭代器
可迭代对象
可迭代对象(Iterable)是一种特殊的 JavaScript 对象,它实现了 迭代器协议。这意味着该对象定义了它自己的迭代行为,可以在 for...of
循环或其他期望可迭代对象的上下文中使用。
迭代器协议,具体来说:
- 实现
[Symbol.iterator]()
方法: 可迭代对象必须实现一个名为[Symbol.iterator]()
的方法。这个方法返回一个迭代器对象,该迭代器对象定义了迭代过程。 - 迭代器对象: 迭代器对象必须实现
next()
方法。每次调用next()
方法时,它都会返回一个对象,该对象有两个属性:value
和done
。value
属性包含当前迭代的值,done
属性是一个布尔值,表示迭代是否结束。 - 可在
for...of
中使用: 由于可迭代对象实现了迭代器协议,因此它们可以在for...of
循环中使用,以及其他期望可迭代对象的上下文中使用。
自定义可迭代对象
const testObj = {}
testObj[Symbol.iterator] = function () {
let i = 0
return {
next: function () {
return {
value: i++,
done: i > 10
}
}
}
}
/**
* 第一次
* {value:0,done:false}
* 第二次
* {value:1,done:false}
*
* ……
*
* 第十次
* {value:9,done:false}
* 第十一次
* {value:10,done:true}
*/
for (let i of testObj) {
console.log(i)
}
可迭代对象的被迭代过程
- 调用可迭代对象的[Symbol.iterator]()方法:该方法被称为迭代器工厂函数,用以生成迭代器对象。
- 调用Symbol.iterator方法返回的迭代器对象:该对象就是迭代器对象,负责迭代过程。
- 使用
next
方法进行迭代: 迭代器对象上有next方法,调用next方法,返回一个迭代结果对象 IteratorResult {value: any, done: boolean}。 - 迭代结果对象:{value: any, done: boolean},如果整个迭代过程完成,也就是所有数据都迭代完了那么 done会变成true,否则就是false。value就是当前迭代的值。如果全部数据都迭代完,value就会变成undefined。
- 迭代器对象的生命周期: 迭代器对象是一次性的,只能做一个整体的迭代,迭代完成之后就会被销毁,不能再次使用。
实验
const arr = [1, 2, 3, 4];
const iter = arr[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
迭代器的提前终止与关闭
迭代器的 return
方法
return
方法: 某些迭代器对象实现了一个可选的return
方法。调用这个方法可以提前终止迭代过程,并关闭迭代器对象。调用return
方法后,迭代器就不能再使用了。- 用途:
return
方法通常用于清理资源或在迭代提前终止时执行特定操作。 - 注意: 并不是所有的迭代器都有
return
方法。例如,数组的默认迭代器就没有return
方法,因此不能通过这种方式提前关闭。
提前终止迭代的情况
- 解构操作未消费所有值(了解即可):
- 当使用解构赋值时,如果没有消费迭代器的所有值,迭代就会提前终止。
- 例如,解构一个数组到有限个变量时,剩余的元素不会被迭代。
for...of
循环的提前退出:for...of
循环可以通过以下方式提前退出:break
: 立即退出循环。continue
: 跳过当前迭代,继续下一个迭代。return
: 从包含循环的函数中返回,终止循环。throw
: 抛出异常,终止循环。
在这些情况下,如果迭代器对象实现了 return
方法,JavaScript 引擎会自动调用 return
方法来关闭迭代器。
实验
const myIterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 3) {
return { value: step, done: false };
}
return { done: true };
},
return() {
console.log('Iterator closed');
return { done: true };
}
};
}
};
const iterator = myIterable[Symbol.iterator]();
for (const value of iterator) {
console.log(value);
if (value === 2) {
break; // 提前退出循环
}
}
// 输出:
// 1
// 2
// Iterator closed
在这个示例中,myIterable
的迭代器实现了 return
方法。当 for...of
循环因 break
提前退出时,return
方法被调用,输出 "Iterator closed"。
迭代器本身也是可迭代的
在 JavaScript 中,迭代器对象本身通常也是可迭代的。这意味着迭代器对象也实现了 [Symbol.iterator]()
方法,并返回自身。当你迭代一个迭代器对象时,实际上是迭代产生该迭代器的可迭代对象。这与直接迭代可迭代对象的效果相同。
有点抽象,注意区分不同颜色的概念,看个实验
const iterable = [1, 2, 3];
const iterator = iterable[Symbol.iterator]();
console.log(iterator[Symbol.iterator]() === iterator); // true
for (const value of iterator) {
console.log(value); // 输出: 1, 2, 3
// 迭代iterator这个迭代器对象与迭代iterable这个可迭代对象,效果是一样的
}
生成器
生成器函数的定义方式:
function * generatorFn()
let generatorFn = function * () {}
let foo = { * generatorFn() {}}
class Foo { * generatorFn (){}}
class Bar { static * generatorFn () {}}
注意:箭头函数是不能用来定义生成器函数的
yield关键字(核心三字诀)
- 起
- 接
- 给
- 停
在例子中体会其含义
生成器函数执行
不会让内部代码执行,只会产生一个生成器对象并返回。
生成器对象
生成器对象控制生成器函数的执行,并提供了一种暂停和恢复代码执行的机制。
生成器对象的特性
- 执行控制器:
- 生成器对象控制生成器函数的执行。调用
next()
方法会使生成器函数的代码从上次暂停的位置继续执行(对应yield的起),直到遇到下一个yield
表达式或函数结束(对应yield的停)。
- 生成器对象控制生成器函数的执行。调用
- 状态:
suspended
(暂停): 当生成器函数执行到yield
表达式并暂停时,生成器对象处于suspended
状态。closed
(关闭): 当生成器函数执行完成或被关闭时,生成器对象处于closed
状态。
next()
方法:- 调用
next()
方法会返回一个对象{ value, done }
。 - 如果生成器函数有一个最终的
return
语句,当done
为true
时,value
是return
返回的值。 - 如果生成器函数执行完毕,后续调用
next()
会返回{ done: true, value: undefined }
。
- 调用
- 自引用:
- 生成器对象是可迭代的,其
[Symbol.iterator]()
方法返回生成器对象自身。这意味着生成器对象可以在for...of
循环中直接使用。
- 生成器对象是可迭代的,其
return()
方法:- 生成器对象实现了
return()
方法,用于提前关闭生成器。调用return()
会使生成器函数立即终止,并将done
设置为true
。
- 生成器对象实现了
throw()
方法:throw()
方法用于在生成器内部抛出错误。这会导致生成器函数在当前位置抛出指定的异常。
仍旧是非常抽象零散的概念,做一个实验看看。
实验
注意:这里“给”的值是return回来的,而不是yield出来的。
生成器与迭代器的结合
一般我们不会写这么抽象的生成器函数。
生成器对象本身就是一种可迭代对象。我们可以直接:
function* generatorFn() {
yield 1;
yield 2;
yield 3;
}
const g = generatorFn();
for (let i of g) {
console.log(i);
}
因为生成器对象首先实现了 [Symbol.iterator]()
方法,其迭代器工厂函数执行返回的其实就是他自己这个生成器对象本身。而且生成其本身就有next()函数,next()返回的也恰好是{ value, done }类型,
符合迭代对象规范。
补充:Symbol新的原始值数据类型
在 JavaScript 中,Symbol
是一种新的原始数据类型,用于创建独一无二的标识符。与其他原始数据类型(如 Number
、String
、Boolean
、Undefined
和 Null
)不同,Symbol
是为避免对象属性名冲突而设计的。
特性和用法
- 唯一性:
- 每个
Symbol
值都是唯一的,即使它们的描述相同。 Symbol
不能与其他类型进行隐式转换。
- 每个
- 创建 Symbol:
- 使用
Symbol()
函数创建,不使用new
关键字。 - 可以提供一个可选的描述字符串,用于调试和日志记录。
- 使用
- 作为对象属性:
Symbol
可以用作对象的属性名,避免属性名冲突。- 使用方括号语法来定义和访问
Symbol
属性。
let s1 = Symbol('foo');
let o = {
[s1]: 'foo val'
};
意义
- 避免属性名冲突:
Symbol
属性不会与字符串属性发生冲突,是定义对象属性的安全方式。
- 用于创建内置方法和属性:
- JavaScript 使用
Symbol
来定义一些内置方法和属性,增强语言的灵活性。
- JavaScript 使用
获取 Symbol 属性
Object.getOwnPropertySymbols()
:- 获取对象的所有
Symbol
属性。
- 获取对象的所有
全局 Symbol 注册表
Symbol.for()
:- 在全局注册表中查找或创建一个
Symbol
。如果已存在,则返回该Symbol
,否则创建一个新的。
- 在全局注册表中查找或创建一个
Symbol.keyFor()
:- 返回一个全局
Symbol
的键(描述),如果不存在则返回undefined
。
- 返回一个全局
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); // 输出: 'foo'
内部方法和属性
JavaScript 定义了一些内置的 Symbol
值,用于改变语言的行为:
Symbol.asyncIterator
: 定义对象的默认异步迭代器。Symbol.hasInstance
: 用于自定义instanceof
操作符的行为。Symbol.isConcatSpreadable
: 指示对象是否可以在数组的concat()
方法中展开。Symbol.iterator
: 定义对象的默认迭代器。- 正则表达式相关:
Symbol.match
: 定义正则表达式的匹配行为。Symbol.replace
: 定义正则表达式的替换行为。Symbol.search
: 定义正则表达式的搜索行为。Symbol.split
: 定义正则表达式的分割行为。
Symbol.species
: 控制派生对象的构造函数。Symbol.toPrimitive
: 定义对象的原始值转换。Symbol.toStringTag
: 改变对象的默认字符串描述。Symbol.unscopables
: 指定对象的哪些属性不会被with
环境绑定。