在传统的网页开发时无需过多思考内存治理,通常也不会产生重大的结果。因为当用户点击链接关上新页面或者刷新页面,页面内的信息就会从内存中清理掉。
随着SPA(Single Page Application)利用的增多,迫使咱们在编码时须要更多的关注内存。因为如果利用应用的内存逐步增多会间接影响到网页的性能,甚至导致浏览器标签页解体。
这篇文章,咱们将钻研JavaScript编码导致内存透露的场景,提供一些内存治理的倡议。

什么是内存透露?

咱们晓得浏览器会把object保留在堆内存中,它们通过索引链能够被拜访到。GC(Garbage Collector) 是一个JavaScript引擎的后盾过程,它能够甄别哪些对象是曾经处于无用的状态,移除它们,开释占用的内存。

本该被GC回收的变量,如果被其余对象索引,而且能够通过root拜访到,这就意味着内存中存在了冗余的内存占用,会导致利用的性能降级,这时也就产生了内存透露。

怎么发现内存透露?

内存透露个别不易觉察和定位,借助浏览器的内建工具能够帮忙咱们剖析是否存在内存透露,和导致呈现的起因。

开发者工具

关上开发者工具-Performance选项卡,能够剖析以后页面的可视化数据。Chrome 和 Firefox 都有杰出的内存剖析工具,通过剖析快照为开发者提供内存的分配情况。

JS导致内存透露的常见情景

未被留神的全局变量

全局变量能够被root拜访,不会被GC回收。一些非严格模式下的局部变量可能会变成全局变量,导致内存透露。

  • 给没有申明的变量赋值
  • this 指向全局对象
function createGlobalVariables() {  leaking1 = 'I leak into the global scope'; // assigning value to the undeclared variable  this.leaking2 = 'I also leak into the global scope'; // 'this' points to the global object};createGlobalVariables();window.leaking1; // 'I leak into the global scope'window.leaking2; // 'I also leak into the global scope'

如何防止?应用严格模式。

闭包

闭包函数执行实现后,作用域中的变量不会被回收,可能会导致内存透露:

function outer() {  const potentiallyHugeArray = [];  return function inner() {    potentiallyHugeArray.push('Hello'); // function inner is closed over the potentiallyHugeArray variable    console.log('Hello');  };};const sayHello = outer(); // contains definition of the function innerfunction repeat(fn, num) {  for (let i = 0; i < num; i++){    fn();  }}repeat(sayHello, 10); // each sayHello call pushes another 'Hello' to the potentiallyHugeArray  // now imagine repeat(sayHello, 100000)

定时器

应用setTimeout 或者 setInterval:

function setCallback() {  const data = {    counter: 0,    hugeString: new Array(100000).join('x')  };  return function cb() {    data.counter++; // data object is now part of the callback's scope    console.log(data.counter);  }}setInterval(setCallback(), 1000); // how do we stop it?

只有当定时器被清理掉的时候,它回调函数外部的data才会被从内存中清理,否则在利用退出前始终会被保留。

如何防止?

function setCallback() {  // 'unpacking' the data object  let counter = 0;  const hugeString = new Array(100000).join('x'); // gets removed when the setCallback returns    return function cb() {    counter++; // only counter is part of the callback's scope    console.log(counter);  }}const timerId = setInterval(setCallback(), 1000); // saving the interval ID// doing something ...clearInterval(timerId); // stopping the timer i.e. if button pressed

定时器赋值给timerId,应用clearInterval(timerId)手动清理。

Event listeners

addEventListener 也会始终保留在内存中无奈回收,直到咱们应用了 removeEventListener,或者增加了监听工夫的DOM被移除。

const hugeString = new Array(100000).join('x');document.addEventListener('keyup', function() { // anonymous inline function - can't remove it  doSomething(hugeString); // hugeString is now forever kept in the callback's scope});

如何防止?

function listener() {  doSomething(hugeString);}document.addEventListener('keyup', listener); // named function can be referenced here...document.removeEventListener('keyup', listener); // ...and here// 或者document.addEventListener('keyup', function listener() {  doSomething(hugeString);}, {once: true}); // listener will be removed after running once

JS内存透露总结

甄别和修复JS内存应用问题是一项有挑战性的工作,编码过程中也要把防止内存透露放在第一位。因为这关系到了利用的性能和用户体验。

本文翻译自 《Causes of Memory Leaks in JavaScript and How to Avoid Them》