内存管理优化


将内存占用量保持在一个较小的值可以让页面性能更好。

优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。如果数据不再必要,那么把它设置为null ,从而释放其引用。这也可以叫作解除引用 。

这个建议最适合全局变量和全局对象的属性。局部变量在超出作用域后会被自动解除引用。

1. 通过const 和let 声明提升性能

ES6增加这两个关键字不仅有助于改善代码风格,而且同样有助于改进垃圾回收的过程。因为const 和let 都以块(而非函数)为作用域,所以相比于使用var ,使用这两个新关键字可能会更早地让垃圾回收程序介入,尽早回收应该回收的内存。在块作用域比函数作用域更早终止的情况下,这就有可能发生。

2. 隐藏类和删除操作

3. 内存泄漏

JavaScript中的内存泄漏大部分是由不合理的引用导致的。

意外声明全局变量是最常见但也最容易修复的内存泄漏问题。

function setName() {
  name = 'Jake';
}

定时器也可能会悄悄地导致内存泄漏。只要定时器一直运行,回调函数中引用的name 就会一直占用内存。

let name = 'Jake';
setInterval(() => {
  console.log(name);
}, 100);

使用JavaScript闭包很容易在不知不觉间造成内存泄漏。以下代码执行后创建了一个内部闭包,只要返回的函数存在就不能清理name ,因为闭包一直在引用着它。

let outer = function() {
  let name = 'Jake';
  return function() {
    return name;
  };
};

4. 静态分配与对象池(静态分配是优化的一种极端形式。如果你的应用程序被垃圾回收严重地拖了后腿,可以利用它提升性能。但这种情况并不多见。大多数情况下,这都属于过早优化,因此不用考虑)

为了提升JavaScript性能,一个关键问题就是如何减少浏览器执行垃圾回收的次数。

浏览器决定何时运行垃圾回收程序的一个标准就是对象更替的速度。如果有很多对象被初始化,然后一下子又都超出了作用域,那么浏览器就会采用更激进的方式调度垃圾回收程序运行,这样当然会影响性能。

假如这个矢量加法函数频繁被调用,那么垃圾回收调度程序会发现这里对象更替的速度很快,从而会更频繁地安排垃圾回收。

该问题的解决方案是不要动态创建矢量对象。

// 原来
function addVector(a, b) {
  let resultant = new Vector();
  resultant.x = a.x + b.x;
  resultant.y = a.y + b.y;
  return resultant;
}
// 修改优化后
function addVector(a, b, resultant) {
  resultant.x = a.x + b.x;
  resultant.y = a.y + b.y;
  return resultant;
}

垃圾回收机制

JavaScript的垃圾回收程序可以总结如下。

  • 离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。
  • 主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它们的内存。
  • 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。JavaScript引擎不再使用这种算法,但某些旧版本的IE仍然会受这种算法的影响,原因是JavaScript会访问非原生JavaScript对象(如DOM元素)。
  • 引用计数在代码中存在循环引用时会出现问题。
  • 解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。