浅谈js闭包

吐槽君 分类:javascript

浅谈js闭包

前言:前几天阿里面试遇到了一题闭包,发现自己对闭包的理解还停留在很浅显的地步,所以重新看了好几篇大佬的文章,在这里浅谈一下对于js闭包的拙见。

闭包的意义

在谈论闭包是什么之前,我们要先明白为什么我们要使用闭包,它能带给我们什么,又会有哪些弊端。在最开始,大家了解到的很多关于闭包的标签可能会有包括内存泄漏,内部函数读取外部数据等等。但是为什么大家还要去用闭包呢?我认为闭包有以下几点意义:

  1. 延长了变量的生命周期,使得部分想要在一段时间内被一直使用而不被内存回收的变量能一直服务于我们的程序。
  2. 能够在外部访问到函数内的变量。在函数内使用变量就可以避免污染全局变量。同时,我们知道js是没有私有变量的,也达到了将变量私有化的目的,保证数据的私有。

闭包存在的原因以及实现

说到闭包就不得不提一下js的垃圾回收机制,正是这种机制的存在,使得闭包能够使用。
引用计数:如果一个对象的引用计数为0,也就是说,没有一个指针能够指向它,则对象就会被回收内存。而js函数里的变量都是临时的,当函数执行结束后就会清除。再次进入函数内就会造成变量初始化,例如:

function add() {
    var counter = 0;
    return counter += 1;
}
add() //1
add() //1
add() //1
 

原本我们预期每次执行add函数时,都会让内部的couter++,也就是希望输出1,2,3。但是现在由于变量的重复初始化和函数执行完毕后的变量清空,我们只能每次都输出1。

接着,我们尝试在函数内部再使用一个函数来对couter进行++,返回couter,但是返回的结果仍然会一直为1。

function add() {
    var counter = 0;
    function plus() {counter += 1;}
    plus();    
    return counter; 
}
add() //1
add() //1
add() //1
 

细心的小伙伴结合上面我们提到的知识可能已经想到了:虽然我们在add函数内部加入了plus函数再返回couter,但是在add结束后,couter和plus函数的引用都消失了,所以这时候它们已经被回收掉了。再次执行函数add也只能是再次初始化进行一样的事情。
这时候聪明的小伙伴可能又想到了,如果我们能把里面的plus函数在外面引用,再在plus函数里留下对add函数内变量的引用,不就可以让他们一直保存在内存中了吗?说干就干,我们来试试:

function newAdd() {
    let num = 0
    function fn() {
      return num++
    }
    return fn
}
let add=newAdd()
add() //1
add() //2
add() //3
 

我们发现现在可以跑通了,让我们来分析一下实现的原理。我们返回了一个函数,在函数内部返回了couter++。当我们在执行add()的时候,其实就是执行了newAdd函数返回的函数fn,在fn中,我们需要去寻找couter,根据js作用域链逐层往上寻找的原理,fn会往newAdd函数内去寻找,找到了num。这个时候,这整个链条就被保存下来了,让它的引用计数不会为0,一直保存在内存中。我们也知道一点,在js中,函数如果不加括号,表示的是指针,指向保存它的内存。如果我们已经确定这一部分变量、函数已经不再被需要了,就要设置js add=null来解除引用。这时候js引擎检测到有一部分内存已经不可达了,就会去释放这部分内存。
如果有小伙伴还想进一步了解闭包,可以去看看执行上下文和作用域链的相关知识,相信你会了解更加深刻。

回复

我来回复
  • 暂无回复内容