内存的生命周期:
1.内存分配:声明变量、函数、对象的时候,js会自动分配内存
2.内存使用:调用的时候,使用的时候
3.内存回收:
垃圾回收机制的方法:
1.标记清除:
1.垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。
2.然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
3.最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
2.引用计数:
1.引用计数的含义是跟踪记录每个值被引用的次数。
2.当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。
3.相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。
4.当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。
致命缺陷:循环引用
js中常见的内存泄漏:
1.全局变量
2.未被清除的定时器和回调
3.闭包
如何避免内存泄漏:
1.减少全局变量的使用
2.使用完数据之后,即时解除引用
3.对象object优化
为了最大限度的实现对象的重用,应该像避使用new语句一样避免使用{}来新建对象。
{“foo”:”bar”}这种方式新建的带属性的对象,常常作为方法的返回值来使用,可是这将会导致过多的内存创建,因此最好的解决办法是:每一次函数调用完成之后,将需要返回的数据放入一个全局的对象中,并返回此全局对象。如果使用这种方式,就意味着每一次方法调用都会导致全局对象内容的修改,这有可能会导致错误的发生。因此,一定要对此全局对象的使用进行详细的注释和说明。
有一种方式能够保证对象(确保对象prototype上没有属性)的重复利用,那就是遍历此对象的所有属性,并逐个删除,最终将对象清理为一个空对象。
cr.wipe(obj)方法就是为此功能而生,代码如下:
// 删除obj对象的所有属性,高效的将obj转化为一个崭新的对象!
cr.wipe = function (obj) {
for (var p in obj) {
if (obj.hasOwnProperty(p))
delete obj[p];
}
};
有些时候,你可以使用cr.wipe(obj)方法清理对象,再为obj添加新的属性,就可以达到重复利用对象的目的。虽然通过清空一个对象来获取“新对象”的做法,比简单的通过{}来创建对象要耗时一些,但是在实时性要求很高的代码中,这一点短暂的时间消耗,将会有效的减少垃圾堆积,并且最终避免垃圾回收暂停,这是非常值得的!
4.数组array优化
将[]赋值给一个数组对象,是清空数组的捷径(例如: arr = [];),但是需要注意的是,这种方式又创建了一个新的空对象,并且将原来的数组对象变成了一小片内存垃圾!实际上,将数组长度赋值为0(arr.length = 0)也能达到清空数组的目的,并且同时能实现数组重用,减少内存垃圾的产生。
5.方法function优化
方法一般都是在初始化的时候创建,并且此后很少在运行时进行动态内存分配,这就使得导致内存垃圾产生的方法,找起来就不是那么容易了。但是从另一角度来说,这更便于我们寻找了,因为只要是动态创建方法的地方,就有可能产生内存垃圾。例如:将方法作为返回值,就是一个动态创建方法的实例。
在游戏的主循环中,setTimeout或requestAnimationFrame来调用一个成员方法是很常见的,例如:
setTimeout(
(function(self) {
return function () {
self.tick();
};
})(this), 16)
每过16毫秒调用一次this.tick(),嗯,乍一看似乎没什么问题,但是仔细一琢磨,每一次调用都返回了一个新的方法对象,这就导致了大量的方法对象垃圾!
为了解决这个问题,可以将作为返回值的方法保存起来,例如:
// at startup
this.tickFunc = (
function(self) {
return function() {
self.tick();
};
}
)(this);
// in the tick() function
setTimeout(this.tickFunc, 16);
相比于每次都新建一个方法对象,这种方式在每一帧当中重用了相同的方法对象。这种方式的优势是显而易见的,而这种思想也可以应用在任何以方法为返回值或者在运行时创建方法的情况当中。
常用的数据类型在内存中占用的大小:
number:8 bytes(字节);
string:每个字符 2 bytes (字节);
boolean:4 bytes(字节);