Javascript闭包

作用域

作用域可以参考:

作用域链

关于作用域链,很多人会感觉费解,但如果你理解了调用栈、执行上下文、词法环境、变量环境等概念,那么你理解起来作用域链也会很容易。所以很是建议你结合前几篇文章将上面那几个概念学习透彻。 其实在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为outer。 当一段代码使用了一个变量时,JavaScript引擎首先会在“当前的执行上下文”中查找该变量,比如上面那段代码在查找myName变量时,如果在当前的变量环境中没有查找到,那么JavaScript引擎会继续在outer所指向的执行上下文中查找。

function bar() {
    console.log(myName)
}

function foo() {
    var myName = "mark"
    bar()
}
var myName = "Tony"
foo()

scope_outer_pointer.png

JS执行过程中,作用域链是由词法作用域决定的。

词法作用域

词法作用域: 词法作用域就是指作用域由代码中函数声明的位置来决定,所以词法作用域是静态的作用域,通过它就可以预测代码在执行过程在如何查找标识符。

Lexical_Environment_overview.png

词法作用域就是代码阶段就决定好的,和函数是怎么调用的没有关系。

闭包

在JavaScript中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是foo,那么这些变量的集合就称为foo函数的闭包。

function foo() {
    var myName = "mark"
    let test1 = 1
    const test2 = 2
    var innerBar = {
        getName:function(){
        console.log(test1)
        return myName
    },
    setName:function(newName){
        myName = newName
    }
    }
    return innerBar
}
var bar = foo()
bar.setName("tony")
bar.getName()
console.log(bar.getName())

这些闭包是如何使用的呢?当执行到bar.setName方法中的 myName = “mark” 这句代码时,JavaScript引擎会沿着“当前执行上下文–>foo函数闭包–>全局执行上下文”的顺序来查找myName变量。

闭包是怎么回收的

理解什么是闭包之后,接下来我们再来简单聊聊闭包是什么时候销毁的。因为如果闭包使用不正确,会很容易造成内存泄漏的,关注闭包是如何回收的能让你正确地使用闭包。通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到⻚面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。 如果引用闭包的函数是个局部变量,等函数销毁后,在下次JavaScript引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么JavaScript引擎的垃圾回收器就会回收这块内存。

所以在使用闭包的时候,你要尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。

参考

Mark24

Everything can Mix.