精读《JS中的内存管理》

JS中的内存管理,感觉就像JS中的一门副科,我们平时不太会忠实,但是一旦出现问题又很棘手,所以可以通过平时的多了解一些JS中的内存管理问题,在写代码中通过一些平时养成的习惯,避免内存泄露的问题。

不管什么语言,内存生命周期基本一致:

1、分配内存;

2、使用分配的内存(读、写);

3、不需要的时候再释放内存。

C语言中,有专门的内存管理接口,像malloc()和free()。JS中没有专门的内存管理接口,所有的内存管理都是自动的。JS创建变量是,自动分配内存,并在不使用的时候,自动释放该内存。这种自动的内存回收,造成了很多JS开发者并不关心内存回收问题,实际上,这会造成许多错误。

关于JS内存回收:

1、引用:

垃圾回收算法主要依赖于引用的概念,在内存管理环境中,一个对象如果有访问另一个对象的权限(隐式或显示),叫做一个对象引用另一个对象。例如:一个JavaScript对象具有对它模型的引用(隐式引用)和对它属性的引用(显示引用)

2、引用计数垃圾收集:

这是简单的垃圾收集算法,此算法吧“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用对象指向该对象,对象将被垃圾回收机制给回收。

示例:

let arr = [1,2,3];

arr = null;//[1,2,3]这时候没有被引用,会被自动回收

3:限制:循环使用:

在接下来的例子中,两个对象被创建并相互引用,就造成了循环引用。它们被调用之后不会离开函数作用域,所以它们已经没有用了,可以被回收了,然而,引用计数算法考虑到它们互相都有至少一次引用,所以不会被回收。

示例:

function f(){

var x = {};

var y = {};

x.p = y;//x引用y

y.p = x;//y引用x

//这里就形成了一个循环引用

}

f();

实际的例子:

var div;

window.onload = function(){

div = document.getElementById("myId");

div.circularReference = div;

div.lotsOfData = new Array(10000).join("*");

};

这个例子中,myId这个DOM元素里的circularReference属性引用了myId,造成了循环引用,IE6,7使用引用计数方式对DOM对象进行垃圾回收,该方式常常造成对象引用时内存发生泄漏。主流浏览器通过使用标记-清除 内存回收算法,来解决这一问题。

4:标记-清除算法

这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。

这个算法鉴定设置一个叫根root的对象(JS中,根是全局对象)。定期的,垃圾回收器将从根开始。找所有从根开始饮用的对象,然后找到这些对象引用的对面,从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。

从2012年起,所有的主流浏览器都使用了标记-清除内存回收算法。所有对JS垃圾回收算法的改进都基于该算法。

5:什么事内存泄露?

本质上讲,内存泄露就是不在被需要的内存,由于某种原因,无法被释放。

6:常见的内存泄露案例:

1:全局变量:

function foo(arg){

bar = "xx";

}

在JS中处理未被声明的变量,上述示例中的bar时,会把bar定义到全局对象中,在浏览器上也就是window上。在页面中的全局变量,只有当页面被关闭后才会被销毁。所以这种写法就会造成内存泄露,但让这个例子中泄露的只是一个简单的字符串,但是实际代码中,往往情况会更糟糕。

另外一种意外创建全局变量的情况:

function foo(){

this.varl = "xx";

}

foo();

foo被调用时,this指向全局变量window。意外的创建了全局变量,造成内存泄露。

我们谈到了一些意外情况下定义的全局变量,代码中也有一些我们明确定义的全局变量。如果使用这些全局变量来暂存大量的数据,记得在使用后,对其重新赋值为null。

2:未销毁的定时器和回调函数:

很多库中,如果使用了观察者模式,都会提供回调方法,来调用一些做后续处理的回调函数。要记得回收这些回调函数。

举一个setInterval的例子:

var serverData = loadData();

setInterval(function(){

var renderer = document.getElementById('renderer');

if(renderer){

renderer.innerHTML(JSON.stringify(serverData));

}

},5000);//5秒调用一次

如果后续renderer元素被溢出,整个定时器实际上没有任何作用,但是如果你不对其进行回收,整个定时器依然存在内存中,不但定时器无法被内存回收,定时器函数中的依赖也无法回收。在这个案例中的serverData也无法被回收。

3、闭包:

在JS开发中,我们会经常使用的闭包,一个内部函数,有权访问包含其的外部函数中的变量。

下面的这种情况,闭包也会造成内存泄露:

var theThing = null;

var replaceThing = function(){

var originalThing = theThing;

var unused = function(){

if(originalThing)//引用包含此函数的外部函数中的变量

console.log("x");

};

theThing = {

longStr:new Array(10000000).join('*'),

someMethod:function(){

console.log("y");

}

};

};

setInterval(replaceThing,1000);

这段代码,每次调用replaceThing时,theThing获得了包含一个巨大的数组和一个对于新闭包someMethod的对象。同时unused是一个引用了originalThing的闭包。

这个范例的关键在于,闭包之间是共享作用域的,尽管unused可能一直没有被调用,但是someMethod可能会被调用,就会导致内存无法对其进行回收,这段代码被反复执行时,内存会持续的增长!

该问题的更多描述课件Meteor团队的文章!

4、DOM引用:

很多时候我们对DOM进行操作,会把DON的引用保存到一个数组或Map中

var elments = {

image:document.getElementById("image");

};

function doStuff(){

elements.image.src = 'http://example.com/image_name.png';

}

function removeImage() {
    document.body.removeChild(document.getElementById('image'));
    // 这个时候我们对于 #image 仍然有一个引用, Image 元素, 仍然无法被内存回收. 
}
上述案例中, 即使我们对于 image 元素进行了移除, 但是仍然有对 image 元素的引用, 依然无法对齐进行内存回收.
另外需要注意的一个点是, 对于一个 Dom 树的叶子节点的引用. 举个例子: 如果我们引用了一个表格中的 td 元素, 一旦在 Dom 中删除了整个表格, 我们直观的觉得内存回收应该回收除了被引用的 td 外的其他元素. 但是事实上, 这个 td 元素是整个表格的一个子元素, 并保留对于其父元素的引用. 这就会导致对于整个表格, 都无法进行内存回收. 所以我们要小心处理对于 Dom 元素的引用.
精读:
ES6中引入 WeakSet 和 WeakMap 两个新的概念, 来解决引用造成的内存回收问题. WeakSet 和 WeakMap 对于值的引用可以忽略不计, 他们对于值的引用是弱引用,内存回收机制, 不会考虑这种引用. 当其他引用被消除后, 引用就会从内存中被释放.
JS 这类高级语言,隐藏了内存管理功能。但无论开发人员是否注意,内存管理都在那,所有编程语言最终要与操作系统打交道,在内存大小固定的硬件上工作。不幸的是,即使不考虑垃圾回收对性能的影响,2017 年最新的垃圾回收算法,也无法智能回收所有极端的情况。
唯有程序员自己才知道何时进行垃圾回收,而 JS 由于没有暴露显示内存管理接口,导致触发垃圾回收的代码看起来像“垃圾”,或者优化垃圾回收的代码段看起来不优雅、甚至不可读。
所以在 JS 这类高级语言中,有必要掌握基础内存分配原理,在对内存敏感的场景,比如 nodejs 代码做严格检查与优化。谨慎使用 dom 操作、主动删除没有业务意义的变量、避免提前优化、过度优化,在保证代码可读性的前提下,利用性能监控工具,通过调用栈定位问题代码。
同时对于如何利用 chrome调试工具, 分析内存泄露的方法和技巧. 可以参考WEB前端教程-精读《2017前端性能优化备忘录》
总结:
即便在 JS 中, 我们很少去直接去做内存管理. 但是我们在写代码的时候, 也要有内存管理的意识, 谨慎的处理可能会造成内存泄露的场景.


原文链接:https://juejin.im/entry/59f9331551882546b15bd2bd?utm_source=gold_browser_extension

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值