1.内存泄漏系统进程不再使用,没有及时释放的内存称为内存泄漏。二、内存泄漏的原因1、意外的全局变量因为js是通过在window对象上创建对变量的引用来处理未声明变量的。直到窗口关闭或页面刷新,变量才会被释放。如果未声明变量缓存了大量数据,会造成内存泄漏//未声明变量functionfn(){a='globalvariable'}fn()//使用this创建的变量(this的重点窗户)。functionfn(){this.a='globalvariable'}fn()解决方法:避免使用严格模式创建全局变量,在文件或函数顶部添加usestrict2,关闭导致内存泄漏原因:closureVariablesinsidefunctions可以读取然后保存在内存中。如果局部变量在使用后不清除,可能会导致内存泄漏。functionfn(){vara="我是一个";返回函数(){console.log(a);};}解决方法:在外部定义事件处理函数,释放闭包,或者定义事件处理函数的外部函数。比如:循环中的函数表达式可以复用,最好放在循环外。//badfor(vark=0;k<10;k++){vart=function(a){//函数对象创建了10次。console.log(a)}t(k)}//goodfunctiont(a){console.log(a)}for(vark=0;k<10;k++){t(k)}t=null3,UncleanedDOMelementreference原因:虽然在别处删除了,但是对象中仍然存在对dom的引用。//在对象中引用DOMvarelements={btn:document.getElementById('btn'),}functiondoSomeThing(){elements.btn.click()}functionremoveBtn(){//移除body中的btn,即移除DOM树中的btndocument.body.removeChild(document.getElementById('button'))//但是此时全局变量elements仍然保留着对btn的引用,btn仍然存在于内存中并且不能被GC回收}解决方法:手动删除,elements.btn=null。4.遗忘定时器或回调定时器中有对dom的引用。即使dom被删除了,定时器还在,所以dom还在内存中。//计时器varserverData=loadData()setInterval(function(){varrenderer=document.getElementById('renderer')if(renderer){renderer.innerHTML=JSON.stringify(serverData)}},5000)//观察或modevarbtn=document.getElementById('btn')functiononClick(element){element.innerHTML="I'minnerHTML"}btn.addEventListener('click',onClick)定时器回调函数还是没有回收(定时会仅在设备停止时回收)。同时,如果serverData存储了大量数据,则无法回收。对于观察者模式,老的IE6无法处理循环引用。今天,即使没有明确删除它们,一旦观察者对象变得不可访问,大多数浏览器也会回收观察者处理程序。解决方法:手动删除timer和dom。removeEventListener移除事件监听器三、内存分配和垃圾回收机制1、内存分配1.1值初始化为了不让程序员费心去分配内存,JavaScript在定义变量的时候就完成了内存分配。变量n=123;//为数字变量分配内存vars="azerty";//为字符串分配内存varo={a:1,b:null};//为对象及其包含的值分配内存//为数组及其包含的值分配内存(就像对象一样)vara=[1,null,"abra"];functionf(a){returna+2;}//给函数(可调用对象)分配内存//函数表达式也可以分配一个对象someElement.addEventListener('click',function(){someElement.style.backgroundColor='blue';},false);1.2通过函数调用分配内存//一些函数调用的结果是分配对象内存:vard=newDate();//分配一个Date对象vare=document.createElement('div');//分配一个DOM元素//一些方法分配新变量或新对象:vars="azerty";vars2=s.substr(0,3);//s2是一个新字符串//因为字符串是不可变的,//JavaScript可能决定不分配内存,//只存储范围[0-3]。vara=["ouaisouais","nannan"];vara2=["一代","南南"];vara3=a.concat(a2);//新数组有四个元素,是a连接a2的结果2.垃圾收集机制高级语言解释器内嵌了一个“垃圾收集器”,其主要工作是跟踪内存的分配和使用,当分配的内存不再使用时,自动释放。这只能是一个大概的过程,因为知道是否还需要一块内存是不确定的(无法通过某种算法解决)。2.1.引用垃圾收集算法在很大程度上依赖于引用的概念。在内存管理的上下文中,如果一个对象可以访问另一个对象(隐式或显式),则它被称为引用另一个对象的对象。例如,一个Javascript对象有对其原型的引用(隐式引用)和对其属性的引用(显式引用)。2.1.1引用计数垃圾收集这是最基本的垃圾收集算法。该算法将“是否不再需要该对象”的定义简化为“该对象是否有其他对象引用它”。如果没有对该对象的引用(零引用),该对象将被垃圾回收。varo={a:{b:2}};//创建了两个对象,一个被引用为另一个的属性,另一个被赋值给变量o//显然,它们都不能被垃圾回收varo2=o;//o2变量是对“这个对象”的第二个引用o=1;//现在,"Thisobject"只有一个对o2变量的引用,"Thisobject"的原始引用o没有varoa=o2.a;//引用“thisobject”的a属性//现在,“thisobject”有两个引用,一个是o2,另一个是oao2="yo";//虽然原来的对象现在是零引用,可以被垃圾回收//但是其属性a的对象仍然被oa引用,所以oa=null不能被回收;//属性a的对象现在也是零引用//它可以被垃圾回收2.1.2限制:循环引用该算法有一个限制:它不能处理循环引用的情况。在下面的示例中,创建了两个对象并相互引用,形成了一个循环。它们被调用后就离开了函数作用域,所以它们是无用的,可以被回收。但是,引用计数算法考虑到它们之间至少有一个引用,所以它们不会被收集。函数f(){varo={};varo2={};o.a=o2;//o引用o2o2.a=o;//o2referencesoreturn"azerty";}f();IE6,7使用引用计数的DOM对象垃圾收集。这种方法在循环引用对象时经常会导致内存泄漏:vardiv;window.onload=function(){div=document.getElementById("myDivElement");div.circularReference=div;div.lotsOfData=newArray(10000).join("*");};在上面的示例中,DOM元素myDivElement中的circularReference属性引用myDivElement,从而导致循环引用。如果该属性未明确删除或设置为null,则引用计数垃圾收集器将始终至少有一个对DOM元素的引用并将其保留在内存中,即使它已从DOM树中删除。如果DOM元素有很多数据(如上面的lotsOfData属性),则这些数据占用的内存将永远不会被释放。2.Mark-sweepalgorithm该算法假设设置了一个名为root的对象(在Javascript中,root是全局对象)。垃圾收集器会周期性地从根开始,查找所有从根引用的对象,然后查找这些对象引用的对象。这个算法比上一个算法要好,因为“零引用的对象”总是不可达的,反之则不是Definitely,参考“CircularReferences”。2012年以来的所有现代浏览器都使用标记清除垃圾收集算法。JavaScript垃圾回收算法的所有改进都是基于标记-清除算法的改进,而不是标记-清除算法本身及其对“是否不再需要某个对象”的简化定义。循环引用不再是问题在上面的示例中,函数调用返回后,两个对象都无法从全局对象访问。因此,它们将被垃圾收集器收集。第二个例子是一样的,一旦div和它的事件处理程序不再可以从根访问,它们将被垃圾收集。限制:那些不能从根对象中查询到的对象将被清除。虽然这是一个限制,但是在实践中我们很少遇到类似的情况,所以开发者也不太关心垃圾回收机制。第四,ES6防止内存泄露,及时清除引用很重要。但是,你记不住那么多,有时候一不小心就忘记了,所以才会有这么多的内存泄漏。考虑到这一点,ES6引入了两种新的数据结构:weakset和weakmap。它们对值的引用不计入垃圾回收机制,即如果其他对象不再引用该对象,垃圾回收机制会自动回收该对象占用的内存。constwm=newWeakMap()constelement=document.getElementById('example')vm.set(element,'something')vm.get(element)在上面的代码中,首先创建一个Weakmap实例。然后,在实例中存储一个DOM节点作为键名,同时在WeakMap中存储一些附加信息作为键值。此时WeakMap中对element的引用是弱引用,不会被纳入垃圾回收机制。//代码1ele.addEventListener('click',handler,false)//代码2constlistener=newWeakMap()listener.set(ele,handler)ele.addEventListener('click',listener.get(ele),false)代码2相比代码1的优势在于:由于监控函数是放在WeakMap中的,一旦dom对象ele消失,其绑定的监控函数handler也会自动消失5.mapsetweakMap与weakSet的区别1.Map对象:None顺序键值对,key必须是字符串map:key可以是任意类型consta=newMap([['a',1],['b',2]])constx={id:1}consty={id:2}constb={}b[x]='bx'b[y]='by'console.log(b[x])//byconsole.log(b[y])//byc=newMap();c.set(x,'cx')c.set(y,'cy')console.log(c.get(x))//cxconsole.log(c.get(y))//cyconsole.log(JSON.stringify([...c.entries()],null,''))[[{"id":1},"cx"],[{"id":2},"cy"]]console.log(JSON.stringify([...c.values()],null,''))["cx","cy"]控制台。日志(JSON.stringify([...c.keys()],空,''))[{"id":1},{"id":2}]2.weakMapweakMap只接受对象作为键,这些对象是弱持有的。如果这个对象被垃圾回收了,weakMap中的键值对也会消失。weakMap没有大小和清晰,不会暴露任何键、值或迭代器c=newWeakMap();c.set(x,'cx')c.set(y,'cy')x=null//可回收y=null//可回收3.Setconsts=newSet()varx={id:1}vary={id:2}s.add(x)s.add(y)console.log(s.size)s.delete(x)s.clear()s.has(x)4.WeakSetWeakSet弱持有,value只能是对象.同理,对象可以回收参考万恶的前端内存泄露和万山的解决方案内存管理阮一峰内存泄露JavaScript内存泄露的4种方式以及如何避免
