面试题答案
一键面试JavaScript迭代器与生成器底层实现原理
- 可迭代协议
- 可迭代协议定义了对象成为可迭代对象的标准。一个对象要成为可迭代对象,必须实现
Symbol.iterator
方法。当这个对象被用于需要可迭代对象的场景(如for...of
循环、Array.from()
等)时,就会调用该对象的Symbol.iterator
方法。该方法返回一个符合迭代器协议的迭代器对象。 - 例如,数组是可迭代对象,因为数组的原型上定义了
Symbol.iterator
方法:
const arr = [1, 2, 3]; const iterator = arr[Symbol.iterator](); 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()
方法。每次调用next()
方法,都会返回一个包含value
和done
两个属性的对象。value
表示当前迭代的值,done
是一个布尔值,当done
为true
时,表示迭代结束。 - 以自定义迭代器为例:
const myIterator = { data: [1, 2, 3], index: 0, next() { if (this.index < this.data.length) { return { value: this.data[this.index++], done: false }; } else { return { value: undefined, done: true }; } } }; console.log(myIterator.next()); // { value: 1, done: false } console.log(myIterator.next()); // { value: 2, done: false } console.log(myIterator.next()); // { value: 3, done: false } console.log(myIterator.next()); // { value: undefined, done: true }
- 迭代器协议定义了迭代器对象的标准。迭代器对象必须拥有
- 生成器
- 生成器是一种特殊的函数,使用
function*
语法定义。生成器函数返回一个生成器对象,这个对象既是可迭代对象,又符合迭代器协议。生成器函数可以暂停和恢复执行,通过yield
关键字来暂停函数执行并返回一个值。 - 例如:
function* myGenerator() { yield 1; yield 2; yield 3; } const gen = myGenerator(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 2, done: false } console.log(gen.next()); // { value: 3, done: false } console.log(gen.next()); // { value: undefined, done: true }
- 生成器对象的
next()
方法不仅可以获取yield
返回的值,还可以向生成器函数内部传递值。当生成器函数暂停在yield
处时,调用next(value)
方法,value
会作为yield
表达式的返回值。
function* myGenerator() { const first = yield 1; const second = yield first + 2; return second * 2; } const gen = myGenerator(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next(3)); // { value: 5, done: false } 这里3作为yield 1的返回值,first为3,所以yield first + 2返回5 console.log(gen.next(4)); // { value: 8, done: true } 4作为yield first + 2的返回值,second为4,所以返回4 * 2 = 8
- 生成器是一种特殊的函数,使用
大规模数据遍历场景下的性能优化
- 避免一次性加载所有数据
- 在处理大规模数据时,使用迭代器和生成器可以按需生成数据,而不是一次性加载到内存中。例如,假设要处理一个非常大的文本文件,每行是一个数据项。可以使用生成器逐行读取文件内容,而不是将整个文件读入内存。
const fs = require('fs'); const readline = require('readline'); function* readLines(filePath) { const rl = readline.createInterface({ input: fs.createReadStream(filePath), crlfDelay: Infinity }); for await (const line of rl) { yield line; } } const filePath = 'largeFile.txt'; const lineGenerator = readLines(filePath); for (const line of lineGenerator) { // 处理每一行数据,无需一次性加载整个文件到内存 console.log(line); }
- 减少中间数据存储
- 当对大规模数据进行一系列操作时,尽量避免创建大量中间数据。例如,对一个很大的数组进行过滤和映射操作,如果使用普通数组方法,会先创建过滤后的数组,再创建映射后的数组。而使用迭代器和生成器,可以在迭代过程中直接进行操作,减少内存占用。
function* filterAndMap(data, filterFunc, mapFunc) { for (const item of data) { if (filterFunc(item)) { yield mapFunc(item); } } } const largeArray = Array.from({ length: 1000000 }, (_, i) => i + 1); const filteredAndMapped = filterAndMap(largeArray, num => num % 2 === 0, num => num * 2); for (const result of filteredAndMapped) { console.log(result); }
- 缓存中间结果
- 如果在迭代过程中有一些计算是重复的,可以缓存这些中间结果。例如,在对大规模数据进行复杂计算时,某些子计算的结果在多个地方会用到。
function* complexCalculation(data) { const cache = new Map(); for (const item of data) { let subResult; if (cache.has(item)) { subResult = cache.get(item); } else { subResult = expensiveSubCalculation(item); cache.set(item, subResult); } yield finalCalculation(subResult); } } function expensiveSubCalculation(num) { // 模拟一个耗时的子计算 return num * num * num; } function finalCalculation(subResult) { return subResult + 10; } const largeData = Array.from({ length: 100000 }, (_, i) => i + 1); const calculationResult = complexCalculation(largeData); for (const result of calculationResult) { console.log(result); }
通过以上对迭代器与生成器底层原理的理解以及在大规模数据场景下的性能优化策略,可以更高效地处理相关业务逻辑。