Stack:用于存放原始数据类型,栈空间由操作系统管理,开发者无需太在意;heap:堆空间由V8引擎管理,可能由于代码问题导致内存泄漏,或者长时间运行后,垃圾回收导致程序运行缓慢;我们可以使用如下代码查看Node.js(运行在node环境下)的内存状态:constformat=function(bytes){return`${(bytes/1024/1024).toFixed(2)}MB`;};constmemoryUsage=process.memoryUsage();console.log(JSON.stringify({rss:format(memoryUsage.rss),//常量In-memoryheapTotal:format(memoryUsage.heapTotal),//总堆空间heapUsed:format(memoryUsage.heapUsed),//使用的堆空间external:format(memoryUsage.external),//与C++对象相关的空间},null,2));常见的内存泄漏场景有四种:全局变量闭包引用事件绑定缓存爆炸全局变量没有用var/let/const声明,会直接绑定到全局对象(Node.js)或window对象(浏览器),虽然是不再使用,不会自动回收;函数t(){arr=newArray(1000);}t();控制台日志(arr);可以看到t函数执行完后,arr还存在于内存中,没有被释放;闭包引用闭包的内存泄漏往往是隐藏的:lettheThing=null;让replaceThing=function(){constnewThing=theThing;constunused=function(){if(newThing)console.log("hi");};//不断修改引用theThing={longStr:newArray(100).join("*"),someMethod:function(){console.log("a");},};//每次输出值都会越来越大console.log(process.memoryUsage().heapUsed);};setInterval(replaceThing,100);运行这段代码,可以看到outputusedheapmemory越来越大,关键是因为在当前的V8实现中,闭包对象是被当前作用域内的所有内部函数作用域共享的,也就是说,theThing.someMethod和unUsed共享同一个闭包上下文,导致theThing.someMethod隐式持有之前newThing的引用,这样每次执行replaceThing函数时,都会执行一次longStr:newArray(1e8).join("*"),并且不会自动回收,导致内存占用越来越多,最终内存泄漏事件绑定事件绑定导致的内存泄漏在浏览器中很常见,通常是因为事件绑定函数没有及时移除,或者是重复绑定事件导致的,比如下面的Vue代码:exportdefault{mounted(){window.addEventListener('resize',function(){//相关操作});}}组件在挂载时监听了窗口的resize事件,但组件销毁时并没有移除该事件。如果这个组件被调用的非常频繁,那么挂载到窗口上的无用事件就会越来越多,导致内存泄漏;可以通过以下方法解决:exportdefault{mounted(){window.addEventListener('resize',this.handleResize)},methods:{handleResize(){...}},destroyed(){window.removeEventListener('resize',this.handleResize)}}缓存爆炸javascript对象的内存缓存可以极大的提升程序的性能,但是很有可能缓存的大小或者数据的过期时间不受控制,导致无效数据仍然在缓存中,这也会导致内存泄漏,例如:constcache={}functionsetCache(){cache[Date.now()]=newArray(100)}setInterval(setCache,100)这段代码会一直在缓存对象中写入缓存数据,但是没有设置缓存失效的代码,最终会导致内存爆掉。如果确实需要内存缓存,强烈推荐使用lru-cachenpm包,可以设置缓存有效期和最大缓存空间,通过LRU淘汰算法避免缓存爆炸。
