上面例子中的数组 bigArray 没有从任何函数中直接返回,因此无法直接访问,但是它却不停地膨胀,取决于我们调用了多少次 function inner() 。
如何避免: 闭包是 JavaScript 语言的特性之一,如果无法避开,那就请注意两点:
- 清楚闭包是何时创建的,以及哪些对象会被保留在内存中;
- 清楚闭包的生命周期和用途(尤其是当做回调函数的时候)
下面的例子中,data 对象会在清除定时器后被 GC 回收 。但我们没有获取 setInterval的返回值,也就没办法用代码清除这个定时器,因此尽管完全没有用到,data.hugeString 也会一直保留在内存中,直到进程结束 。
function setCallback() { const data = { counter: 0, hugeString: new Array(100000).join('x') }; return function cb() { data.counter++; // data 对象现在已经属于回调函数的作用域了 console.log(data.counter); }}setInterval(setCallback(), 1000); // 没法停止定时器了
如何避免: 对于生命周期不确定的回调函数,我们应该:- 注意被定时器回调函数引用的对象
- 使用定时器返回的句柄,在必要时清除它
function setCallback() { // 分开定义变量 let counter = 0; const hugeString = new Array(100000).join('x'); // setCallback执行完即可被回收 return function cb() { counter++; // 只剩 counter 位于回调函数作用域 console.log(counter); }}const timerId = setInterval(setCallback(), 1000); // 保存定时器 ID// 执行某些操作 ...clearInterval(timerId); // 停止定时器
4 事件监听器活动的事件监听器会阻止作用域内的变量被 GC 回收 。事件监听器一直处于活动状态,直到用 removeEventListener() 显式移除,或者关联的 DOM 元素被移除 。对于有些事件来说,监听器需要一直保留,直到页面被销毁 。比如按钮点击事件,我们可能需要重复使用 。但是,有时候我们希望某个事件只执行特定次数 。
const hugeString = new Array(100000).join('x');document.addEventListener('keyup', function() { // 匿名监听器无法移除 doSomething(hugeString); // hugeString 会一直处于回调函数的作用域内});
上面例子中的事件监听器用了匿名函数,这样就没法用removeEventListener()移除了 。同时,document元素也无法删除,因此事件回调函数内的变量会一直保留,哪怕我们只想触发一次事件 。如何避免: 事件监听器不再需要时,要记得解除绑定 。使用具名函数方式获取引用,通过removeEventListener()解除绑定 。
function listener() { doSomething(hugeString);}document.addEventListener('keyup', listener); document.removeEventListener('keyup', listener);
如果事件监听器只需要执行一次,addEventListener()可以接受第三个参数,是一个配置对象 。指定{once: true},监听器函数会在事件触发一次执行后自动移除(匿名函数也可以) 。document.addEventListener('keyup', function listener(){ doSomething(hugeString);}, {once: true}); // 执行一次后自动移除事件监听器
5 缓存如果持续不断地往缓存里增加数据,没有定时清除无用的对象,也没有限制缓存大小,那么缓存就会像滚雪球一样越来越大 。let user_1 = { name: "Kayson", id: 12345 };let user_2 = { name: "Jerry", id: 54321 };const mapCache = new Map();function cache(obj){ if (!mapCache.has(obj)){ const value = `${obj.name} has an id of ${obj.id}`; mapCache.set(obj, value); return [value, 'computed']; } return [mapCache.get(obj), 'cached'];}cache(user_1); // ['Kayson has an id of 12345', 'computed']cache(user_1); // ['Kayson has an id of 12345', 'cached']cache(user_2); // ['Jerry has an id of 54321', 'computed']console.log(mapCache); // ((…) => "Kayson has an id of 12345", (…) => "Jerry has an id of 54321")user_1 = null; //Garbage Collectorconsole.log(mapCache); // ((…) => "Kayson has an id of 12345", (…) => "Jerry has an id of 54321") // 依然在缓存里
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 自己设置的WiFi密码忘记了怎么办?教你三招找回来
- JavaScript的Array.flat函数深入探讨
- 茶园的灾后管理,龙岩市茶叶技术人员深入茶区指导冬季茶园管理
- 年夜饭上,教你这4道硬菜做法,3分钟就出锅,端上桌倍有面
- 白鞋穿久了容易变脏,教你一招轻松清洗,再也不用担心白鞋变脏了
- 一招教你识别葡萄酒品质高低
- 不出门能打印社保证明吗?能!手把手教你
- 多少年的茶算古树茶,教你招识别真假古树茶
- 家里的WIFI网络总是被蹭网怎么办?我教你如何杜绝蹭网
- 不用重装系统,教你将win7系统重置回初始状态