闭包
描述:闭包就是能够读取其他函数内部变量的函数。
闭泡的特性:
- 函数内再嵌套函数
- 内部函数可以引用外层的参数和变量
- 参数和变量不会被垃圾回收机制回收
创建闭包
提示:创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域。
例如:
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 输出: 1
console.log(counter()); // 输出: 2
console.log(counter()); // 输出: 3
闭包的使用
提示:闭包可以用来实现缓存(也称为记忆化),用于存储和重用计算结果,以避免重复计算。缓存通常用于那些执行成本高昂的函数,如复杂的计算、网络请求或数据库查询。
function createCacheCalculator(func) {
// 创建一个缓存对象
const cache = {};
// 返回一个闭包,它具有访问外部函数变量的权限
return function(...args) {
// 创建一个缓存键,通常可以使用参数的字符串表示来生成
const cacheKey = JSON.stringify(args);
// 检查缓存中是否存在该键的结果
if (cacheKey in cache) {
console.log('使用缓存值:', args);
return cache[cacheKey];
} else {
// 计算结果并存储到缓存中
const result = func.apply(this, args);
cache[cacheKey] = result;
console.log('新的值:', args);
return result;
}
};
}
// 示例:使用缓存的计算器
const cachedFactorial = createCacheCalculator((n) => {
if (n === 0 || n === 1) return 1;
return n * cachedFactorial(n - 1);
});
console.log(cachedFactorial(5)); // 计算新值
console.log(cachedFactorial(5)); // 使用缓存值
console.log(cachedFactorial(10)); // 计算新值
内存泄露
提示:JavaScript内存泄漏是指由于不正确的内存管理而导致的内存占用不断增加,最终导致应用程序性能下降甚至崩溃的问题
例如:
-
未正确清理事件监听器: 如果在DOM元素上添加了事件监听器,但是忘记在不需要时将其删除,就会导致内存泄漏。确保在不需要监听事件时,使用removeEventListener方法将监听器从元素上移除。
// 添加事件监听器 element.addEventListener('click', handleClick); // 移除事件监听器 element.removeEventListener('click', handleClick);
-
闭包引用: 当一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量时,如果这个内部函数被外部作用域之外的东西引用,就会导致闭包。如果闭包持续存在,外部函数的变量将无法被垃圾回收,从而导致内存泄漏。确保在不需要的时候,解除对闭包的引用。
function outerFunction() { var someVariable = 'Hello'; // 内部函数引用外部变量 function innerFunction() { console.log(someVariable); } return innerFunction; } var closure = outerFunction(); // 当不再需要闭包时,解除引用 closure = null;
-
定时器未清除: 如果设置了循环定时器或者延时定时器,并且在不需要时未清除它们,将会导致内存泄漏。确保在不再需要定时器时,使用clearInterval或clearTimeout将其清除。
// 设置定时器 var timer = setInterval(function() { // 执行操作 }, 1000); // 当不再需要定时器时,清除它 clearInterval(timer);
-
大对象引用: 如果将一个大对象赋值给一个全局变量或者缓存中,并且在不需要时未清除这个引用,将会导致内存泄漏。确保在不再需要这些大对象时,将其引用置为null,以便垃圾回收器能够释放内存。
// 将大对象赋值给全局变量 var largeObject = { /* 大对象 */ }; // 当不再需要大对象时,将引用置为null largeObject = null;
-
DOM节点未正确清理: 当移除DOM节点时,如果节点上仍然存在事件监听器、引用或者数据,就会导致内存泄漏。确保在移除DOM节点时,清理所有相关的引用。
// 移除DOM节点 element.parentNode.removeChild(element);
解决内存泄露方法
提示:内存泄漏发生在不再被引用的数据没有被垃圾回收器回收时
例如:
- 缓存大小限制: 限制缓存的大小,当达到最大容量时,可以移除最老的条目。这可以通过使用一个队列(先进先出)或者最近最少使用(LRU)算法来实现。
- 缓存键的有效管理: 确保缓存键的唯一性和准确性,避免因为键的不准确而导致无法从缓存中移除不需要的条目。
- 缓存条目的生命周期: 为缓存条目设置一个过期时间,使用定时器或者在特定条件下清除过期的缓存数据。
- 监听相关事件: 如果缓存的数据依赖于外部资源(如数据库条目、API数据等),可以监听这些资源的变化事件,并在数据变更时清除或更新缓存。
- 垃圾回收的触发: 在确定不再需要缓存时,手动删除缓存对象或设置为null,以允许垃圾回收器回收内存。
- WeakMaps的使用: 使用WeakMap来存储缓存数据,WeakMap的键是弱引用,当键不再被其他对象引用时,WeakMap中的条目会被垃圾回收器自动移除。
- 使用第三方库: 使用已经实现缓存策略的第三方库,如lodash的memoize函数,这些库通常提供了良好的内存管理机制。
- 定期清理缓存: 定期检查缓存并清理不再需要的条目,特别是在应用程序的生命周期事件(如页面导航或组件卸载)中进行清理。
- 避免缓存大型对象: 如果可能,避免缓存大型对象或数据集,或者只缓存数据的关键部分。
- 缓存数据的序列化: 考虑缓存数据的大小,避免缓存大量未压缩的数据,可以使用序列化方法减少存储的数据量。
- 监控内存使用: 使用开发工具监控内存使用情况,及时发现内存泄漏的迹象。
- 使用请求响应模型: 对于Web应用,可以在请求结束时清理与请求相关的缓存。
解决闭包的方法
-
手动清除缓存: 提供一个清除缓存的方法,允许开发者在适当的时候手动清除缓存。
function createCacheCalculator(func) { const cache = {}; const calculator = function(...args) { // ...(缓存逻辑) }; calculator.clearCache = function() { cache = {}; }; return calculator; }
-
设置缓存的生命周期: 如果缓存与某个特定的对象或组件相关联,可以在对象或组件被销毁时清除缓存。
class SomeComponent { constructor() { this.cachedCalculator = createCacheCalculator(func); } componentWillUnmount() { this.cachedCalculator.clearCache(); } }
-
使用 WeakMap: 使用 WeakMap 来存储缓存数据,这样当没有其他引用指向缓存的键时,WeakMap 中的对象可以被垃圾回收器回收。
function createCacheCalculator(func) { const cache = new WeakMap(); // ...(缓存逻辑,使用 WeakMap API) }
-
缓存自动过期: 为缓存项设置一个过期时间,当缓存项达到一定的时间后,自动从缓存中移除。
一个简单的LRU缓存实现示例function createCacheCalculator(func) { const cache = {}; return function(...args) { const key = JSON.stringify(args); if (key in cache) { const cached = cache[key]; if (Date.now() - cached.timestamp > CACHE_EXPIRATION_TIME) { delete cache[key]; } else { return cached.value; } } // ...(计算并缓存新值) }; } const CACHE_EXPIRATION_TIME = 60 * 60 * 1000; // 1 hour
-
使用第三方库: 使用第三方库,如 lodash 的 memoize 函数,这些库通常提供了缓存清除的机制。
-
依赖注入: 通过依赖注入的方式,将缓存对象作为参数传递给 createCacheCalculator 函数,这样当需要清除缓存时,只需重新注入一个新的缓存对象即可。
一个简单的LRU缓存实现示例:
class LRUCache {
constructor(limit) {
this.cache = {};
this.size = 0;
this.limit = limit;
}
get(key) {
if (key in this.cache) {
this.touch(key);
return this.cache[key];
}
}
set(key, value) {
if (this.size >= this.limit) {
this.evict();
}
this.cache[key] = value;
this.size++;
this.touch(key);
}
touch(key) {
if (!(key in this.cache)) {
return;
}
const value = this.cache[key];
delete this.cache[key];
this.cache[key] = value;
}
evict() {
const key = Object.keys(this.cache)[0];
delete this.cache[key];
this.size--;
}
}