唉,来总结总结垃圾回收吧(虽然我可能总结得很小白哈哈哈)。
为什么存在垃圾回收?
因为系统分配给浏览器的内存通常比分配给桌面软件的要少很多,分配给移动浏览器的就更少了。这出于安全考虑,为了避免运行大量JavaScript的网页而耗尽系统内存导致操作系统崩溃。这个内存限制不仅影响变量分配,也影响调用栈以及能够同时在一个线程中执行的语句数量。
总的来说就是:将内存占用量保持在一个较小的值可以让页面性能更好。
垃圾回收的方法
JS的垃圾回收的方法主要是两个:标记清理和引用计数。
标记清理
这是JS最常用的垃圾回收策略。当变量进入执行上下文,比如在函数内部声明一个变量时,这个变量就会被加上存在于上下文中的标记。而在上下文中的变量,逻辑上讲,永远不应该释放他们的内存,因为只要上下文中的代码在运行,就有可能用到它们。当变量离开上下文时,也会被加上离开上下文的标记。
给变量加标记的方式有很多种。(这个总结不了哈哈)。垃圾回收在程序运行时,会标记内存中存储的所有变量。然后,它会将所在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何上下文中的变量都访问不到他们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回他们的内存。
引用计数
声明变量时给他赋一个引用值,引用值为1.如果同一个值又被复制给另一个变量,那么这个值的引用数加一。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减一。党一个值的引用数为0时就会被垃圾回收程序回收。
但是引用计数有个缺点,那就是循环引用 。所谓循环引用,就是对象A有一个指针指向B,而对象B也引用对象A。比如:
function problem() {
let objA = new Object();
let objB = new Object();
objA.someOtherObject = objB;
objB.oanotherObject = objectA;
}
在这个例子中,objA和objB通过各自的属性相互引用,意味着他们的引用数都是2。在标记清理策略下,这不是问题,因为在函数结束后,这两个对象都不在作用域中。而在引用计数策略下,objA和objB在函数结束后还会存在,因为他们的引用数永远不会变成0.
优化
所以目前基本的浏览器都使用的是标记清理进行垃圾回收。但是全局变量和全局对象的属性的标记永远不会被标记为离开上下文(除非关闭浏览器或者页面),那么这时垃圾回收就不能对他们进行回收了。那么全局变量过多就会占用大量内存,这如何优化呢?答案就是当数据不再需要时,就把她设置为null,从而释放其引用。
比如:
function(name) {
let localPerson = new Object();
localPerson.name = name;
return localPerson;
}
let globalPerson = createPerson('yyy');
//解除引用
globalPerson = null;
在上面的代码中,变量globalPerson保存着createPerson()函数调用返回的值。在createPerson()内部,localPerson创建一个对象并给它添加了一个name属性。然后,localPerson作为函数值被返回,并被赋值给globalPerson。localPerson在createPerson()执行完成超出上下文后会自动被解除引用,不需要显示处理。但globalPerson是一个全局变量,应该在不再需要时手动解除其引用,最后一行就是这么做的。
不过解除对一个值的引用并不会导致相关内存被回收。解除引用的关键在于确保相关的值已经不再上下文里了,因此它在下一次垃圾回收时会被回收。
通过const、let声明提升变量
ES6新增的这两个关键字也可以有助于改进垃圾回收的过程。因为const和let都以块(而非函数)为作用域。所以相比于使用var,使用这两个新关键字可能会更早地让垃圾回收程序介入,尽早回收该应回收的内存。在块作用域比函数作用域更早终止的情况下,这就有可能发生。
此外还有啥隐藏类、删除操作、对象池啥的就不是我这小小实习生可以看得懂的了....