返回顶部
首页 > 资讯 > 前端开发 > JavaScript >JavaScript中怎么防范内存泄漏
  • 741
分享到

JavaScript中怎么防范内存泄漏

2024-04-02 19:04:59 741人浏览 安东尼
摘要

这篇文章给大家介绍javascript中怎么防范内存泄漏,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。浏览器将对象保留在堆内存中,通过引用链可从根对象到达这些对象。垃圾回收器(GC)是

这篇文章给大家介绍javascript中怎么防范内存泄漏,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

浏览器将对象保留在堆内存中,通过引用链可从根对象到达这些对象。垃圾回收器(GC)是 JavaScript  引擎中的一个后台进程,它可以识别无法到达的对象,将其删除,并回收相应的内存。

JavaScript中怎么防范内存泄漏

引用链 - GC - 对象关系图

当内存中本应在垃圾回收循环中被清理的对象,通过另一个对象意外的引用从而维持可访问状态,就会发生内存泄漏。将多余的对象保持在内存中,会导致应用程序内部的内存使用量过大,进而影响性能。

JavaScript中怎么防范内存泄漏

内存泄露

如何判断代码是否存在内存泄漏呢?内存泄漏通常比较隐蔽,难以发现和定位。造成内存泄漏的 JavaScript  代码看上去挺正常,浏览器在运行的时候也不会抛出错误。如果发现页面性能越来越差,通常是内存泄漏的征兆,可以通过浏览器内置的工具判断是否存在内存泄漏,并分析出原因。

最快的方法是查看浏览器的任务管理器(注意,不是操作系统的任务管理器)。它提供了浏览器运行中的所有 tab 页和进程的资源使用情况,比如内存占用、CPU  占用和进程 ID 等。Chrome 的任务管理器可通过 Shift+Esc 快捷键打开,Firefox  可在地址栏输入about:perfORMance打开。

如果页面都没有任何交互,内存占用却越来越多,很可能存在泄漏。

JavaScript中怎么防范内存泄漏 

Chrome 任务管理器

浏览器 DevTools 则提供了更丰富的内存管理功能。可以在 Chrome 的性能面板录制页面运行情况,查看可视化的性能分析数据。

JavaScript中怎么防范内存泄漏

Chrome 性能面板

除此之外,Chrome 和 Firefox 的 DevTools  还有专门的内存工具用于分析内存使用情况。通过比较连续的内存快照,可以看出内存分配情况。

通过前面的分析,内存泄露的根本原因就是代码在无意之中引用了本该被 GC 回收的对象。那么,哪些情况容易造成内存泄露呢?

1、意外的全局变量

全局变量一直处于可访问状态,不会被 GC 回收。在非严格模式下,有时会不小心让局部变量变成全局变量。

  • 给未声明的变量赋值

  • 使用指向全局对象的 this

function createGlobalVariables() {     leaking1 = '变成全局变量了'; // 给未声明的变量赋值     this.leaking2 = '这也是全局变量'; // 'this' 指向全局对象 }; createGlobalVariables(); window.leaking1; // '变成全局变量了' window.leaking2; // '这也是全局变量'

如何避免: 严格模式 ("use strict") 会避免意外的全局变量,以上代码在严格模式下会报错。

2、闭包

函数作用域变量在函数执行完后会被清理,前提是在函数外部没有引用它。闭包会让变量一直处于被引用状态,即使它的执行上下文和作用域已经不存在了。

function outer() {     const Array = [];     return function inner() {         bigArray.push('Hello');          console.log('Hello');     }; }; const sayHello = outer(); // 包含了对 inner 的引用  function repeat(fn, num) {     for (let i = 0; i < num; i++){         fn();     } } repeat(sayHello, 10); // 每次调用 sayHello 都会添加 'Hello' 到potentiallyHugeArray   // 如果是10万次呢?阔怕:repeat(sayHello, 100000)

上面例子中的数组 bigArray 没有从任何函数中直接返回,因此无法直接访问,但是它却不停地膨胀,取决于我们调用了多少次 function  inner()。

如何避免: 闭包是 JavaScript 语言的特性之一,如果无法避开,那就请注意两点:

  • 清楚闭包是何时创建的,以及哪些对象会被保留在内存中;

  • 清楚闭包的生命周期和用途(尤其是当做回调函数的时候)

3、定时器

在 setTimeout 或 setInterval 的回调函数中引用某些对象,是防止被 GC  回收的常见做法。如果在代码里设置循环定时器(setTimeout也能像setInterval一样定时重复执行,只要设置成递归调用),只要定时器还在运行,回调函数中的对象就会一直保持在内存中。

下面的例子中,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); // ((&hellip;) => "Kayson has an id of 12345", (&hellip;) => "Jerry has an id of 54321") user_1 = null;   //Garbage Collector console.log(mapCache); // ((&hellip;) => "Kayson has an id of 12345", (&hellip;) => "Jerry has an id of 54321") // 依然在缓存里

上面的例子中,缓存依然保留了user_1 的数据。因此我们需要把不再使用的数据从缓存中删除。

可能的解决方案: 为了解决这个问题,可以使用 WeakMap。 WeakMap  是一种数据结构,它只用对象作为键,并保持对象键的弱引用,如果这个对象被置空了,相关的键值对会被 GC 自动回收。

let user_1 = { name: "Kayson", id: 12345 }; let user_2 = { name: "Jerry", id: 54321 }; const weakMapCache = new WeakMap();  function cache(obj){     // 代码跟前一个例子相同,只不过用的是 weakMapCache      return [weakMapCache.get(obj), 'cached']; }  cache(user_1); // ['Kayson has an id of 12345', 'computed'] cache(user_2); // ['Jerry has an id of 54321', 'computed'] console.log(weakMapCache); // ((&hellip;) => "Kayson has an id of 12345", (&hellip;) => "Jerry has an id of 54321"} user_1 = null;   // Garbage Collector  console.log(weakMapCache); // ((&hellip;) => "Jerry has an id of 54321") - 第一条记录已被 GC 删除

6、分离的 DOM 元素

如果 DOM 节点被 JavaScript 代码直接引用,即使从 DOM 树分离,也不会被 GC 回收。

下面的例子中,removeChild() 达不到预期效果,堆快照会显示htmlDivElement处于分离状态,因为有个变量指向了这个div。

function createElement() {     const div = document.createElement('div');     div.id = 'detached';     return div; }  // 即使调用了deleteElement() ,依然保存着 DOM 元素的引用 const detachedDiv = createElement(); document.body.appendChild(detachedDiv); function deleteElement() { document.body.removeChild(document.getElementById('detached')); }  deleteElement(); // 堆快照显示: detached div#detached

如何避免: 一种方法是把DOM 引用限制为局部作用域。

function createElement() {...} //  // DOM 引用位于函数作用域内  function appendElement() {     const detachedDiv = createElement();     document.body.appendChild(detachedDiv); }  appendElement();  function deleteElement() {      document.body.removeChild(document.getElementById('detached')); }  deleteElement();

关于JavaScript中怎么防范内存泄漏就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

--结束END--

本文标题: JavaScript中怎么防范内存泄漏

本文链接: https://lsjlt.com/news/69910.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • JavaScript中怎么防范内存泄漏
    这篇文章给大家介绍JavaScript中怎么防范内存泄漏,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。浏览器将对象保留在堆内存中,通过引用链可从根对象到达这些对象。垃圾回收器(GC)是...
    99+
    2024-04-02
  • javascript中怎么防止内存泄漏
    小编给大家分享一下javascript中怎么防止内存泄漏,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧! ...
    99+
    2024-04-02
  • JavaScript中内存泄漏怎么办
    这篇文章主要介绍JavaScript中内存泄漏怎么办,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、什么是内存泄漏?程序的运行需要内存。只要程序提出要求,操作系统或者运行时(run...
    99+
    2024-04-02
  • ThreadLocal内存泄漏怎么预防
    这篇文章主要介绍“ThreadLocal内存泄漏怎么预防”,在日常操作中,相信很多人在ThreadLocal内存泄漏怎么预防问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”ThreadLocal内存泄漏怎么预防...
    99+
    2023-06-29
  • 怎么排查Javascript内存泄漏
    这篇文章主要讲解了“怎么排查Javascript内存泄漏”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么排查Javascript内存泄漏”吧!如何判断我的应用发生了内存泄漏为了证明螃蟹的听...
    99+
    2023-07-02
  • 怎么避免JavaScript内存泄漏
    这篇文章主要介绍“怎么避免JavaScript内存泄漏”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“怎么避免JavaScript内存泄漏”文章能帮助大家解决问题。一、什么是内存泄漏JavaScrip...
    99+
    2023-06-30
  • JavaScript中内存泄漏指的是什么
    内存泄漏是指程序中一个对象被分配到内存中既不能使用,又不能回收,留在了堆内存中就称为内存泄漏,当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,...
    99+
    2024-04-02
  • JavaScript中的内存泄漏的原因
    目录前言内存泄漏的场景全局变量闭包DOM元素引用总结前言 JavaScript的内存泄漏指的是一些不再需要的对象仍然占用着内存,导致内存使用量持续增加,甚至造成浏览器崩溃或性能下降。...
    99+
    2023-05-18
    JavaScript 内存泄漏
  • JavaScript内存泄漏和内存溢出是什么
    本篇内容主要讲解“JavaScript内存泄漏和内存溢出是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JavaScript内存泄漏和内存溢出是什么”吧! ...
    99+
    2024-04-02
  • JavaScript中内存泄漏的示例分析
    这篇文章主要介绍了JavaScript中内存泄漏的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1、识别方法chrome在performance中查看。开启开发工具P...
    99+
    2023-06-15
  • JavaScript内存泄漏实例分析
    这篇文章主要讲解了“JavaScript内存泄漏实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“JavaScript内存泄漏实例分析”吧!js 内存泄...
    99+
    2024-04-02
  • Java中怎么引入内存泄漏
    Java中怎么引入内存泄漏,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. 什么是内存泄漏内存泄漏的定义:应用程序不再使用对象,但是垃圾收集器不能删除它们,因为它们正在被引用...
    99+
    2023-06-16
  • Android中的内存泄漏
    什么是内存泄漏 长生命周期的对象持有了短生命周期的对象,从而导致短生命周期的对象不能被释放 垃圾回收机制 垃圾回收机制分为:引用计数法、可达性分析法 引用计数法(有循环引用的问...
    99+
    2022-06-06
    内存泄漏 Android
  • Java 中的内存泄漏
    什么是 Java 中的内存泄漏? 当应用程序持有不再需要的对象引用时,就会发生 Java 内存泄漏。这些意外的对象引用阻止内置的 Java 垃圾收集机制释放这些对象消耗的内存,最终导致致命的OutOfMemoryError。 简而言之,...
    99+
    2023-10-11
    java jvm 开发语言
  • java怎么读取大文件防止内存泄漏
    在Java中,可以使用java.io包中的BufferedReader和FileReader类来读取大文件,同时避免内存泄漏的问题。...
    99+
    2023-09-12
    java
  • Linux协程与内存泄漏的预防
    在Linux中使用协程时,需要注意内存泄漏的预防措施,以下是一些建议: 使用内存池:使用内存池可以减少内存分配和释放的次数,从而减少内存碎片和提高内存的利用率。可以使用一些开源的内存池库,如jemalloc、tcmalloc等,也可以自...
    99+
    2024-08-06
    linux
  • javascript内存泄漏有哪些原因
    本篇内容介绍了“javascript内存泄漏有哪些原因”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! ...
    99+
    2024-04-02
  • 剖析 JavaScript 内存泄漏的根源
    内存泄漏是指 JavaScript 对象或变量在不再需要时仍然被引用,导致应用程序的内存不断增长。这对 Web 应用程序尤其有害,因为它可能会导致性能下降,甚至崩溃。 检测内存泄漏 检测内存泄漏的第一步是使用浏览器工具(如 Chrome ...
    99+
    2024-04-02
  • JavaScript内存泄漏的情况是怎样的
    这期内容当中小编将会给大家带来有关JavaScript内存泄漏的情况是怎样的,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1、意外的全局变量。function foo(arg) {&n...
    99+
    2023-06-25
  • C++ 技术中的内存管理:如何防止内存泄漏?
    c++++ 内存管理中防止内存泄漏的最佳实践包括:1. 使用智能指针(自动释放内存);2. 正确使用 new 和 delete(成对使用,避免悬空指针);3. 使用 raii(资源超出作...
    99+
    2024-05-01
    内存泄漏 内存管理 c++ 作用域
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作