当前位置: 首页 > 科技观察

如何防止JavaScript内存泄漏

时间:2023-03-17 10:34:48 科技观察

通常,忽略内存管理不会对传统网页造成明显的后果。这是因为,在用户刷新页面后,内存数据被清除了。但是随着SPA(SinglePageApplication)的流行,我们不得不更加关注页面的内存管理。用户很少刷新SPA上的页面。随着页面停留时间的增加,内存可能会占用越来越多的内存,至少会影响页面性能,严重时可能会导致标签页崩溃。在本文中,我们将探讨JavaScript中内存泄漏的常见原因以及如何改进内存管理。浏览器将对象保存在堆内存中,可以通过引用链从根对象访问这些对象。垃圾收集器(GarbageCollector,GC)是JavaScript引擎中的一个后台进程,用于识别不可达对象,将其删除,并回收相应的内存。ChainingofReferences-GC-ObjectDiagram当内存中应该在垃圾回收周期中清理的对象仍然可以通过来自另一个对象的意外引用访问时,就会发生内存泄漏。在内存中保留冗余对象会导致应用程序内部内存使用过多,从而影响性能。内存泄漏如何判断代码是否存在内存泄漏?内存泄漏通常是隐蔽的,很难发现和定位。导致内存泄漏的JavaScript代码看起来很好,浏览器在运行时不会抛出错误。如果发现页面性能越来越差,通常是内存泄漏的征兆。可以使用浏览器自带的工具来判断是否存在内存泄漏并分析原因。最快的方法是查看浏览器的任务管理器(注意,不是操作系统的任务管理器)。它提供了浏览器中运行的所有标签页和进程的资源使用情况,例如内存使用率、CPU使用率和进程ID。Chrome的任务管理器可以通过Shift+Esc打开,Firefox可以通过地址栏输入about:performance打开。如果页面没有任何交互,但内存使用量在增加,则很可能存在泄漏。Chrome任务管理器浏览器DevTools提供了更丰富的内存管理功能。您可以在Chrome的性能面板中记录页面的运行状态,查看可视化的性能分析数据。ChromePerformancePanel除此之外,Chrome和Firefox的DevTools都有专门的内存工具,用于分析内存使用情况。通过比较连续的内存快照可以看出内存分配情况。通过前面的分析,内存泄漏的根本原因是代码无意中引用了应该被GC回收的对象。那么,哪些情况容易导致内存泄漏呢?1.意外的全局变量全局变量总是可以访问的,不会被GC回收。在非严格模式下,局部变量有时会不小心变成全局变量。给未声明的变量赋值使用这个指向全局对象的函数createGlobalVariables(){leaking1='成为一个全局变量';//给未声明的变量赋值this.leaking2='这也是一个全局变量';//'this'指向全局对象};createGlobalVariables();window.leaking1;//'成为全局变量'window.leaking2;//'这也是一个全局变量'如何避免:严格模式("usestrict")会避免意外的全局变量,上面的代码在严格模式下会报错。2、闭包函数的作用域变量在函数执行完后会被清理掉,前提是没有在函数外引用。即使变量的执行上下文和作用域不再存在,闭包也会保持对变量的引用。functionouter(){constArray=[];returnfunctioninner(){bigArray.push('Hello');console.log('Hello');};};constsayHello=outer();//包含对内部函数repeat(fn,num){for(leti=0;i"Kaysonhasanidof12345",(...)=>"Jerryhasanidof54321")user_1=null;//GarbageCollectorconsole.log(mapCache);//((...)=>"Kaysonhasanidof12345",(…)=>"Jerryhasanidof54321")//还在缓存中上面的例子中,缓存中仍然保留着user_1的数据。所以我们需要从缓存中删除不再使用的数据。可能的解决方案:为了解决这个问题,可以使用Wea??kMap。WeakMap是一种只使用对象作为键并保持对对象键的弱引用的数据结构。如果对象被清空,相关的键值对会被GC自动回收。letuser_1={name:"Kayson",id:12345};letuser_2={name:"Jerry",id:54321};constweakMapCache=newWeakMap();functioncache(obj){//代码同上例,但使用的是weakMapCachereturn[weakMapCache.get(obj),'cached'];}cache(user_1);//['Kaysonhasanidof12345','computed']cache(user_2);//['Jerryhasanidof54321','computed']console.log(weakMapCache);//((...)=>"Kaysonhasanidof12345",(...)=>"Jerryhasanidof54321"}user_1=null;//GarbageCollectorconsole.log(weakMapCache);//((...)=>"Jerryhasanidof54321"")-第一条记录已经被GC删除6.DetachedDOMelements如果一个DOM节点被JavaScript代码直接引用,即使它从DOM树中被detach了,也不会被删除被GC回收,在下面的例子中,removeChild()无法达到预期,堆快照会显示HTMLDivElement被detached,因为有一个变量指向div。functioncreateElement(){constdiv=document.createElement('div');div.id='detached';returndiv;}//即使调用了deleteElement(),仍然保存DOM元素的引用constdetachedDiv=createElement();文档。body.appendChild(detachedDiv);functiondeleteElement(){document.body.removeChild(document.getElementById('detached'));}deleteElement();//堆快照显示:detacheddiv#detached如何避免:一种方法是把DOM引用仅限于本地范围。functioncreateElement(){...}////DOM引用位于函数作用域中身体。removeChild(document.getElementById('分离'));}deleteElement();总结定位和解决JavaScript内存问题对于关键的前端应用程序来说是一项具有挑战性的任务。因此,了解典型内存泄漏的原因并从源头上避免是内存管理的必要工作。希望本文总结的六大内存泄漏来源能给你启发,在写代码时未雨绸缪。