一、可迭代对象(Iterable object)
Javascript 可迭代对象是指实现了Symbol.iterator
方法的对象,该方法返回一个迭代器对象,可以通过迭代器对象来遍历对象中的元素。常见的可迭代对象包括数组、字符串、Map、Set等。可以使用for...of
循环或者使用迭代器的next
方法来遍历可迭代对象中的元素。
二、Symbol.iterator
Symbol.iterator是一个内置的Symbol对象,它是用来定义一个对象的默认迭代器的。当一个对象被迭代时(比如使用for…of循环),它的Symbol.iterator方法会被调用,返回一个迭代器对象。这个迭代器对象具有next()方法,用于在对象中的元素上进行迭代。
通过实现Symbol.iterator方法,我们可以使一个对象成为可迭代的,从而可以使用for…of循环或者其他迭代器方法来遍历对象中的元素。常见的可迭代对象包括数组、字符串、Map、Set等。
下面是一个简单的示例,展示了如何使用Symbol.iterator来实现一个可迭代对象:
const days = {start: new Date('2023-10-01'),end: new Date('2023-10-05'),[Symbol.iterator]() {return {current: this.start,last: this.end,next() {if(this.current.getTime() <= this.last.getTime()) {const value = this.currentthis.current = new Date(this.current.getTime() + 24 * 3600 * 1000)return {done: false,value}} else {return {done: true}}}}}
}for(let day of days) {console.log(day)
}
为了让 days对象可迭代(也就让 for…of 可以运行)我们需要为对象添加一个名为 Symbol.iterator
的方法(一个专门用于使对象可迭代的内建 symbol)。
当 for…of 循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) —— 一个有 next 方法的对象
。
当 for…of 循环希望取得下一个数值,它就调用这个对象的 next() 方法。
next() 方法返回的结果的格式必须是 {done: Boolean, value: any},当 done=true 时,表示循环结束,否则 value 是下一个值。
在这里我们也可以结合yield
来实现:
const days = {start: new Date('2023-10-01'),end: new Date('2023-10-05'),*[Symbol.iterator]() {let current = this.startlet last = this.endwhile(current.getTime() <= last.getTime()) {value = currentcurrent = new Date(current.getTime() + 24 * 3600 * 1000)yield value}}
}for(let day of days) {console.log(day)
}
迭代对象的实现方式:
① 可迭代对象必须实现 Symbol.iterator 方法。objSymbol.iterator 的结果被称为 迭代器(iterator)。由它处理进一步的迭代过程。
②一个迭代器必须有 next() 方法,它返回一个 {done: Boolean, value: any} 对象,这里 done:true 表明迭代结束,否则 value 就是下一个值。
Symbol.iterator 方法会被 for…of 自动调用,但我们也可以直接调用它。
注:内建的可迭代对象例如字符串和数组,都实现了 Symbol.iterator。
三、yeild
在JavaScript中,yield
是ES6(ECMAScript 6)中的一种关键字,用于生成器函数中。通过yield
,我们可以在生成器函数中暂停执行并返回一个迭代器对象给调用者,再次调用时可以从上一次暂停的地方继续执行。
yield 的应用场景包括但不限于以下几个方面:
1.惰性求值
通过生成器函数和 yield 关键字,可以实现惰性求值,即只在需要时才计算值。这对于处理大量数据或者需要耗时计算的场景非常有用。
在 JavaScript 中,生成器函数中的 yield语句可以实现惰性求值。惰性求值是一种延迟计算的策略,即只在需要值的时候才进行计算。生成器函数的 yield语句可以用于实现这种惰性求值的特性。
下面是一个示例,演示了如何在生成器函数中使用 yield 实现惰性求值:
function* lazyEvaluation() {let result1 = yield calculateResult1();console.log(result1); // 这里会输出第一个结果let result2 = yield calculateResult2();console.log(result2); // 这里会输出第二个结果
}function calculateResult1() {console.log("Calculating result 1");return 100;
}function calculateResult2() {console.log("Calculating result 2");return 200;
}let generator = lazyEvaluation();// 调用 next() 时,并不会立即计算结果,而是在需要值的时候才进行计算
let next1 = generator.next(); // 这里会输出 "Calculating result 1"
let next2 = generator.next(); // 这里会输出第一个结果,然后输出 "Calculating result 2"
let next3 = generator.next(); // 这里会输出第二个结果
在上面的示例中,lazyEvaluation 是一个生成器函数,其中使用了 yield 语句来实现惰性求值。当调用生成器函数的 next() 方法时,计算并返回结果的操作被延迟到真正需要值的时候才进行。这样可以节省计算资源,并且使得程序更加高效。
2.异步编程
yield 可以与异步操作结合使用,例如使用 Promise 对象。通过 yield 可以简化异步操作的编写,使代码更加清晰易懂。
在 JavaScript 中,生成器函数中的 yield 语句可以与异步编程结合使用,从而实现更加灵活和可读性更高的异步代码。通过结合生成器函数和异步操作,可以避免回调地狱(callback hell)和使用 Promise 的 then 方法时出现的嵌套问题。
下面是一个示例,演示了如何在生成器函数中使用 yield 语句实现异步编程:
function fakeAsyncAPI(value, delay) {return new Promise(resolve => {setTimeout(() => {resolve(value);}, delay);});
}function* asyncOperation() {let result1 = yield fakeAsyncAPI('First result', 1000);console.log(result1); // 输出: First resultlet result2 = yield fakeAsyncAPI('Second result', 500);console.log(result2); // 输出: Second result
}function runAsyncOperation() {let generator = asyncOperation();function handleAsyncOperation(result) {let next = generator.next(result);if (next.done) {return;}next.value.then(res => {handleAsyncOperation(res);});}handleAsyncOperation();
}runAsyncOperation();
3.无限序列
生成器函数可以用于创建无限序列,例如斐波那契数列、无限递增的整数等。
在 JavaScript 中,可以使用生成器函数和 yield 语句来创建无限序列。生成器函数可以用于按需生成序列中的元素,而不需要提前将整个序列存储在内存中。
下面是一个示例,演示了如何使用生成器函数和 yield 语句创建一个无限序列:
function* infiniteSequence() {let i = 0;while (true) {yield i;i++;}
}let generator = infiniteSequence();console.log(generator.next().value); // 输出: 0
console.log(generator.next().value); // 输出: 1
console.log(generator.next().value); // 输出: 2
// 以此类推,可以无限地获取序列中的下一个元素
4.状态机
yield 可以用于实现状态机,通过不同的 yield 值来表示不同的状态。
在 JavaScript 中,可以使用生成器函数和 yield 语句来实现状态机。状态机是一种用于描述对象在不同状态下的行为的数学模型,它包含有限个状态以及在这些状态之间的转换。
下面是一个简单的示例,演示了如何使用生成器函数和 yield 语句来实现一个简单的状态机:
function* stateMachine() {let state = 'start';while (true) {let input = yield state;if (input === 'go') {state = 'running';} else if (input === 'stop') {state = 'stopped';}}
}let machine = stateMachine();
console.log(machine.next().value); // 输出: start
console.log(machine.next('go').value); // 输出: running
console.log(machine.next('stop').value); // 输出: stopped
在上面的示例中,stateMachine 是一个生成器函数,它模拟了一个简单的状态机。每次调用生成器函数的 next() 方法时,会返回当前状态,并等待传入下一个输入。根据输入的不同,状态机会根据规则转换到不同的状态。
通过使用生成器函数和 yield 语句,我们可以很容易地实现状态机的逻辑。这种方式使得状态机的代码更加清晰和易于理解,同时也可以方便地实现状态之间的转换逻辑。
5.协程函数
实现函数暂停和恢复的协程模式。
在 JavaScript 中,yield 关键字通常与生成器函数(Generator
Function)一起使用,用于实现协程(Coroutine)。协程是一种并发编程的模型,它允许在不同的执行上下文之间进行切换,并且可以保存和恢复执行状态。通过使用yield 和生成器函数,可以实现一种简单的协程模型。
在协程函数中,yield 可以暂停函数的执行,并且可以返回一个值给调用者。调用者可以在需要时恢复协程的执行,并且可以传入一个值作为协程函数中 yield 的结果。
下面是一个简单的示例,展示了如何使用生成器函数和 yield 实现协程函数:
function* coroutineFunction() {let result = yield "First";console.log(result);result = yield "Second";console.log(result);
}let coroutine = coroutineFunction();console.log(coroutine.next()); // 输出: { value: 'First', done: false }
console.log(coroutine.next("Hello")); // 输出: Hello// 输出: { value: 'Second', done: false }
console.log(coroutine.next("World")); // 输出: World// 输出: { value: undefined, done: true }
在这个示例中,coroutineFunction 是一个生成器函数,通过 yield 实现了协程的暂停和恢复。在调用 coroutine.next() 时,协程函数会在每个 yield 处暂停,并返回一个对象,包含当前的值和是否执行完毕的标志。调用 coroutine.next(value) 时,传入的值会作为上一个 yield 的结果,并且协程函数会继续执行直到下一个 yield 处。
四、可迭代(iterable)和类数组(array-like)
这两个术语看起来差不多,但其实大不相同。
Iterable 如上所述,是实现了 Symbol.iterator
方法的对象。
Array-like 是有索引
和 length 属性
的对象,所以它们看起来很像数组。
① 一个可迭代对象也许不是类数组对象。
② 一个类数组对象可能不可迭代。比如:let arrayLike = { // 有索引和 length 属性 => 类数组对象0: "Hello",1: "World",length: 2 };// Error (no Symbol.iterator) for (let item of arrayLike) {}
③ 一个对象,可能既是类数组对象,又是可迭代对象。比如,字符串既是可迭代的(for…of 对它们有效),又是类数组的(它们有数值索引和 length 属性)。