JavaScript性能优化 - 引用计数算法实现原理

139 阅读3分钟

针对引用计数算法来说,它的核心思想其实就是在内部去通过一个引用计数器来维护当前对象的引用数。从而去判断改对象的引用数值是否为 0 ,来决定它是否是一个垃圾对象。当这个数值是 0 的时候,那么 GC 就开始工作将其所在的一个对象空间进行回收和释放在使用。这里提到一个名称叫引用计数器,关于它要又一个小小的印象。因为相对于其他的一些 GC 算法来讲,也正是由于引用计数器的存在导致了引用计数在执行效率上可能与其他的GC算法有所差别。这个说完以后还需要再思考一下,我们引用的这样一个数值什么时候会发生改变。所以在这里它给出的一个规则是这样的,当某一个对象它的一个引用关系去发生改变的时候,引用计数器就会主动的修改当前这个对象所对应的引用数值。什么叫做引用关系发生改变呢?例如说我们的代码里面现在有一个对象空间,目前来说又一个变量名指向它。这个时候我们就把数值加一,如果说在这个时候又多了一个对象还指向它我们就把这个数值再加一。如果是减少的情况下,就减一就可以了。当我们发现这样一个引用数字为 0 的时候,GC 就会立即工作然后将当前的对象空间进行回收。说完这样一段的原理之后,我们再通过一些简单的代码来说明下这个引用关系发生改变的一种情况。

const user1 = {age:11}
const user2 = {age:22}
const user3 = {age:33}

const nameList = [user1.age, user2.age, user3.age]
function fn () {
    num1 = 1
    num2 = 2
}
fn()

从全局的角度去考虑,我们会发现 window 的下面是可以直接找到 user1、user2、user3、nameList;从变量这个角度出发,同时在 fn 函数里面 num1、num2 由于前面没有设置关键字所以它同样是被挂载在当前的这样一个 window 对象下。所这个时候对于这些变量来说,它们的引用计数肯定都不是 0,然后紧接着我们做一些修改。比如我们在 fn 函数里面 num1、num2 前面添加上关键字声明,就意味着 num1、num2 只能在 fn 块级作用域里起效果。所以一但当 fn 调用执行结束过后,我们在外部全局的地方出发就不能够再找到 num1、num2 了。这个时候 num1、num2 它们身上的引用计数就会回到 0,此时此刻只要是 0 的情况情况下 GC 就会立即开始工作。将它们当作垃圾去进行一个对象回收,也就是说这个时候函数执行完成以后它们所在的一个内存空间就会被回收掉。

function fn () {
    const num1 = 1
    const num2 = 2
}
fn()

由于在这个地方nameList 里面其实刚好都指向了 user 对象空间,所以当我们脚本即使执行完一边以后它后头一看 user 系列对象空间其实都还被人引用着,此时的引用计数就不是0 ,那么这个时候就不会被当做垃圾去进行回收。这块就是关于引用计数这样一个算法在实现过程中,它所遵循的一些基本原理。

总结:靠着当前对象身上一个引用计数的数值来判断是否为 0 ,从而决定它是不是一个垃圾对象。