惰性求值
惰性求值(Lazy Evaluation)是一种计算策略,它将表达式的求值延迟到真正需要结果的时候。这种技术可以显著提高性能,特别是在处理大数据集时。
什么是惰性求值?
// 立即求值(Eager Evaluation)
const eager = [1, 2, 3, 4, 5]
.map(x => x * 2) // 立即计算: [2, 4, 6, 8, 10]
.filter(x => x > 5); // 立即计算: [6, 8, 10]
// 惰性求值(Lazy Evaluation)
// 只有在需要时才计算
惰性序列的实现
class LazySequence {
constructor(generator) {
this.generator = generator;
}
static of(iterable) {
return new LazySequence(function* () {
yield* iterable;
});
}
static range(start, end) {
return new LazySequence(function* () {
for (let i = start; i < end; i++) {
yield i;
}
});
}
static repeat(value) {
return new LazySequence(function* () {
while (true) {
yield value;
}
});
}
map(fn) {
const self = this;
return new LazySequence(function* () {
for (const item of self.generator()) {
yield fn(item);
}
});
}
filter(predicate) {
const self = this;
return new LazySequence(function* () {
for (const item of self.generator()) {
if (predicate(item)) {
yield item;
}
}
});
}
take(n) {
const self = this;
return new LazySequence(function* () {
let count = 0;
for (const item of self.generator()) {
if (count >= n) break;
yield item;
count++;
}
});
}
takeWhile(predicate) {
const self = this;
return new LazySequence(function* () {
for (const item of self.generator()) {
if (!predicate(item)) break;
yield item;
}
});
}
drop(n) {
const self = this;
return new LazySequence(function* () {
let count = 0;
for (const item of self.generator()) {
if (count >= n) {
yield item;
}
count++;
}
});
}
concat(other) {
const self = this;
return new LazySequence(function* () {
yield* self.generator();
yield* other.generator();
});
}
flatMap(fn) {
const self = this;
return new LazySequence(function* () {
for (const item of self.generator()) {
yield* fn(item).generator();
}
});
}
// 强制求值
toArray() {
return [...this.generator()];
}
reduce(fn, initial) {
let acc = initial;
for (const item of this.generator()) {
acc = fn(acc, item);
}
return acc;
}
find(predicate) {
for (const item of this.generator()) {
if (predicate(item)) {
return item;
}
}
return undefined;
}
*[Symbol.iterator]() {
yield* this.generator();
}
}
使用示例
// 处理大数据集,但只取前10个
const result = LazySequence.range(1, 1000000)
.map(x => {
console.log(`Mapping ${x}`);
return x * x;
})
.filter(x => {
console.log(`Filtering ${x}`);
return x % 2 === 0;
})
.take(10)
.toArray();
console.log(result);
// 只会计算前10个符合条件的值,而不是100万个
无限序列
// 斐波那契数列
const fibonacci = new LazySequence(function* () {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
});
// 获取前10个斐波那契数
console.log(fibonacci.take(10).toArray());
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// 获取第一个大于1000的斐波那契数
const firstLarge = fibonacci.find(x => x > 1000);
console.log(firstLarge); // 1597
// 素数序列
function* primes() {
const sieve = new Set();
for (let n = 2; ; n++) {
if (!sieve.has(n)) {
yield n;
for (let m = n * n; m < n * n + 10000; m += n) {
sieve.add(m);
}
}
}
}
const primeSequence = new LazySequence(primes);
console.log(primeSequence.take(20).toArray());
// [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
惰性属性
function lazyProperty(obj, name, initializer) {
let value;
let initialized = false;
Object.defineProperty(obj, name, {
get() {
if (!initialized) {
value = initializer();
initialized = true;
}
return value;
},
enumerable: true,
configurable: true
});
}
// 使用
const config = {};
lazyProperty(config, 'database', () => {
console.log('Initializing database connection...');
return { host: 'localhost', port: 5432 };
});
// 第一次访问时才初始化
console.log(config.database); // 初始化并返回
console.log(config.database); // 直接返回缓存值
记忆化与惰性求值
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (!cache.has(key)) {
cache.set(key, fn(...args));
}
return cache.get(key);
};
};
// 惰性计算属性
const createLazyObject = (computations) => {
const cache = {};
const obj = {};
Object.keys(computations).forEach(key => {
Object.defineProperty(obj, key, {
get: memoize(() => {
console.log(`Computing ${key}...`);
return computations[key]();
})
});
});
return obj;
};
// 使用
const data = createLazyObject({
expensive1: () => {
// 耗时计算
return Array(1000000).fill(0).map((_, i) => i * 2);
},
expensive2: () => {
// 另一个耗时计算
return Array(1000000).fill(0).reduce((a, b, i) => a + i, 0);
}
});
// 只在访问时计算
console.log(data.expensive1.slice(0, 5)); // 计算并返回
console.log(data.expensive1.slice(0, 5)); // 从缓存返回
流式处理
// 模拟文件行处理
function* readLines(fileContent) {
const lines = fileContent.split('\n');
for (const line of lines) {
yield line;
}
}
// 处理日志文件
const processLogFile = (content) => {
return LazySequence.of(readLines(content))
.map(line => line.trim())
.filter(line => line.length > 0)
.filter(line => line.includes('ERROR'))
.map(line => {
const match = line.match(/ERROR: (.+)/);
return match ? match[1] : line;
})
.take(100) // 只处理前100个错误
.toArray();
};
// 处理大型 CSV
const processCSV = (content) => {
const lines = LazySequence.of(readLines(content));
const headers = lines.take(1).toArray()[0].split(',');
return lines
.drop(1)
.map(line => line.split(','))
.map(values => {
return headers.reduce((obj, header, i) => {
obj[header] = values[i];
return obj;
}, {});
})
.filter(row => row.id && row.name); // 过滤无效行
};
性能对比
// 立即求值 - 创建多个中间数组
console.time('eager');
const eager = Array(1000000)
.fill(0)
.map((_, i) => i)
.map(x => x * 2)
.filter(x => x % 3 === 0)
.slice(0, 10);
console.timeEnd('eager');
// 惰性求值 - 只计算需要的值
console.time('lazy');
const lazy = LazySequence.range(0, 1000000)
.map(x => x * 2)
.filter(x => x % 3 === 0)
.take(10)
.toArray();
console.timeEnd('lazy');
// 惰性求值通常快得多,因为避免了创建大型中间数组
最佳实践
- 大数据集处理:使用惰性求值避免内存问题
- 无限序列:生成器适合表示无限序列
- 延迟计算:只在需要时计算昂贵操作
- 组合操作:链式调用保持惰性直到最终求值
- 注意副作用:惰性求值中副作用的执行时机可能不确定