Javascript的世界中,隐藏了很多内存陷阱,不能得到合理释放的内存会埋下各种隐患,本文旨在以实用角度去解读Js涉及到的内存,且看勇士如何斗恶龙~
本文可以看做是之前那篇勇士斗恶龙之没那么复杂的Js闭包的后续篇,在思考闭包中内存的问题时,有了写此文的冲动.
学以致用,从实用的角度出发我们最需要关注的就是内存回收用什么用处?我们日常工作中好像不需要我们自己去处理Javascript中的内存,它会自动回 收的.如果你问Javascript内存回收,一定会有TX鄙视的告诉你,这个会自动回收,不需要我们自己处理.
Javascript中的内存,真的不需要我们关注吗???
通常这种自问自答的结果,就是会给出一个与问题相反的答案,也就是当然需要!Javascript虽然会自动处理内存,但不是完美的,它存在一定的缺陷.另一方面,我们不能因为无知而无畏,因为可能很多莫名其妙的问题就是这样出现的.
内存-那些我们忽略的就是我们要拯救的
在平时的编码中我们无时无刻不在执行这样的一个过程,分配内存 -> 使用内存 -> 回收内存.如此反复的过程在不断持续着,每一个数字,字符串,对象,数组,方法都占用着计算机的内存.前端编程因为你的Js代码大部分运行在客户端浏览 器,所以虽然你没有占用服务器的内存,可是你占用了使用者的内存.即使在硬件配置越来越高的今天,也不能完全保证我们产品的使用者不是古老的机器,况且你 也应该考虑到移动手机的性能.也不是说每一台机器都配有4G以上的内存供你挥霍,我一直认为好的程序员对待内存要保持一个吝啬的心态.
Javascript在分配内存这一过程中,会根据不同的数据类型进行分配.像基本数据类型会分配在栈内存,引用数据类型分配在堆内存中.引用对于理解内存是一个很重要的概念,我最早对引用的理解来自于C里面的指针,最好找资料仔细了解一下.
1
2
3
4
|
var obj = {name: "benze" }; var car = obj; //这里car就是指向了obj所创建的那个对象所在的内存空间,也就是说此时obj和car都指向了同一块内存. //在内存管理的环境中,如果一个对象有权限去访问另一个对象,就叫做一个对象引用另一个对象 |
我们平时在使用定义变量,函数或者对象的时候,都在进行各种的内存分配,但是通常不需要写代码去回收,因为我们知道它是自动回收的.这里我得强调,自动不代表我们就不要考虑内存回收了.
使用内存-也就是那些读读写写
关于内存的使用,我的理解就是对于已经开辟好内存空间的那些值,进行一些读写操作.这一过程中可能还有对象引用的改变等等,因为这个不是本文重点,不加以赘述.
内存回收 === 屠龙之术?
话说古时候有人散尽家财学的屠龙之术,技成之后却发现无龙可屠.那Javascript既然有着自动回收的内存管理,我们学习内存回收岂不也是一样,反正
它能正常回收就行呗,我们管它是怎么回收的呢.但是问题是我之前提到过,Javascript的垃圾回收机制有一定的局限性和缺陷,有一些情况会使得内存
得不到释放而持续增加,这时候我们就需要人为的处理它.
引用计数的回收机制
上文提到过引用的概念,Javascript的内存回收的算法主要就依赖于引用,当代码生成一个新的内存驻留项时(如一个对象),系统就会为它开辟一块内
存空间.因为这个对象可能会被传递给其他函数,或者对象.所以可能很多代码都会指向这个对象的内存空间.javascript的垃圾回收器跟踪这些指向,
当最后一个指向都被断开废弃的时候,这个对象所占用的空间就会被释放.
这是一种比较简单并且清晰的算法,看上去感觉没什么问题,但是如果出现这种情况呢?
1
2
3
4
|
var a = {name: 'a' }; var b = {name: 'b' }; a.bname = b b.aname = a; |
代码看着很别扭是吧,但是如果真有这种情况呢,彼此引用.这样的情况,javascript的的回收就对a和b没有办法了.对于这种循环引用,实在是各类垃圾自动回收的缺陷.
真实情况的内存无法回收
也许上面说的那种情况对于你来说永远不可能发生,平时注意点也许就避开了,但是总有些情况,也许你写了很久自己都没发现.在稍微旧一点版本的IE下,Javascript的对象是通过标记清除的,BOM和DOM对象却是通过引用计数,涉及到DOM或者BOM的时候就容易出现循环引用.上代码瞅瞅:
1
2
3
4
5
6
|
<span></span>$(document).ready( function (){ var div = document.getElementById( "mydiv" ); div.onclick = function (){ console.log( "div" ); } });<span></span> |
当指定的单击事件处理程序时,创建了一个在其封闭的环境中包含div变量的闭包环境.而div也包含一个指向闭包的引用(onclick属性自身),这就导致了内存都不能得到释放.当然解决方法很简单:
1
2
3
4
5
6
7
|
$(document).ready( function (){ var div = document.getElementById( "mydiv" ); div.onclick = saydiv; }); function saydiv(){ console.log( "div" ); } |
此时因为saydiv函数不在包含div的引用,所以没有形成循环,内存可以得到释放.
可能很多人都知道将一个对象置为null,那么它的内存就会回收.这是因为变量的指向了一个null,那么它原来指向的那块内存空间就会因为没有被指向,或者说没有被引用,而被垃圾回收掉.
标记清除-手动内存回收竟然还是屠龙之术?
从2012年起现代浏览器中,对于Javascript垃圾回收的机制进行了更新.不再使用引用计数的算法,而是改为使用标记清除的方式.比如定义一个变 量,那么当它进入执行环境时,会被垃圾回收器标记为"进入环境",当其离开环境比如函数执行完毕的时候,标记为"离开环境".垃圾回收机器就会在这些"离 开环境"的变量中挑选出来需要回收掉的变量用于释放内存.
这存在一个挑选标准,它不会再去计 算引用的数量.而是从全局对象(根节点)开始寻找,找到所有可获得的对象和所有不可获得的对象.也就是它从之前判断"对象是否被需要"变成"对象是否可以 获得".这么理解,零引用的对象总是不可获得的,但是不可能获得的对象不一定零引用.
如此除了在比较低版本的IE的情况下,Javascript的自动回收机制就足以应付大多数情况了.在高级一点的IE中对于内存回收也有很大的进步,所以还是推荐大多数情况下不要手工的回收垃圾.
尾笔
本来不打算写这段尾笔,不过为了避免本文有虎头蛇尾的嫌疑,还是要补充说明一下.首先是大部分现代浏览器都已经对内存做了很好的处理,所以大多数情况下不 需要我们手工执行.其次本文的目的在于归纳总结,而不是非要写出什么特殊东东.最后写此文也是为了给我自己和看过的人提个醒:
- 不要因为是Web的前端就忽视了内存这个因素,更别以为所有人的电脑都会配有2G乃至4G的内存.
- 本文最大的作用是指出一些可能存在的陷阱,不是告诉大家掉进去怎么办,而是怎样避免掉进去.
- 循环引用,闭包,DOM操作,这3点是我认为最容易造成内存问题.
- 别因为你从来没遇到Javascript内存的问题而忽视乃至忘却它,优秀的程序员应该吝啬计算机的资源.