漫漫前端路之JS基础——作用域链篇

132 阅读3分钟

作用域链

在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用成为outer。

function bar() {
    console.log(myName)
}
function foo() {
    var myName = "极客邦"
    bar()
}
var myName = "极客时间"
foo()

上述代码在寻找myName变量时,如果在当前的变量没有找到,那么js引擎会继续在outer所指向的执行上下文中查找。 image.png bar 函数和 foo 函数的 outer 都是指向全局上下文的,这也就意味着如果在 bar 函数或者 foo 函数中使用了外部变量,那么 JavaScript 引擎会去全局执行上下文中查找。我们把这个查找的链条就称为作用域链。

词法作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。 image.png 因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:foo 函数作用域—>bar 函数作用域—>main 函数作用域—> 全局作用域。 词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。

块级作用域中变量查找

image.png 执行到bar中if需要查找test,首先是在bar的执行上下文中查找,但是因为bar函数的执行上下文中没有定义test变量,所以根据词法作用域的规则,下一步就在bar函数的外部作用域中查找,也就是全局作用域。

闭包

function foo() {
    var myName = "极客时间"
    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("极客邦")
bar.getName()
console.log(bar.getName())

当执行到foo函数内部的return innerBar这行代码时调用栈的情况: image.png 根据词法作用域规则,内部函数getName和setName总是可以访问它们的外部函数foo中的变量,所以当innerBar对象返回给全局变量bar时,虽然foo函数已经执行结束,但是getName和setName函数依然可以使用foo函数中的变量myName和test1。所以当foo函数执行完成后,整个调用栈状态如下图: image.png 可以看到,foo执行完后,其执行上下文从栈顶弹出了,但是由于返回的setName和getName方法中用了foo函数的变量,所以这两个变量依旧保存在内存中。这像极了这两个方法的一个专属背包,专属的原因是因为除了这两个方法之外,其他任何地方都是无法访问该背包的,所以我们就可以把这个背包称为foo函数的闭包。 可以在开发者工具”来看看闭包的情况,打开 Chrome 的“开发者工具”,在 bar 函数任意地方打上断点,然后刷新页面 image.png

闭包回收

  • 通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。
  • 如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
  • 使用原则:若闭环需要持续使用,则可以作为全局变量存在,若使用频率不高,占内存较大的话,尽量以局部变量存在。

资料来源

time.geekbang.org/column/intr…