Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

变量的查找 —— 作用域链与闭包 #64

Open
lovelmh13 opened this issue Jun 7, 2021 · 0 comments
Open

变量的查找 —— 作用域链与闭包 #64

lovelmh13 opened this issue Jun 7, 2021 · 0 comments

Comments

@lovelmh13
Copy link
Owner

lovelmh13 commented Jun 7, 2021

变量的查找 —— 作用域链与闭包

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

根据执行上下文我们可以知道:

img

此时的调用栈是这样的。

那么 console.log(myName) 会打印什么值呢?答案是 "极客时间",而不是 "极客邦"。为什么不是从调用栈里自下而上的找变量,而是直接使用了全局执行上下文的变量呢?因为其实在变量环境中,又一个外部引用 outer,他来指定当前上下文的外部的执行上下文。

img

这个,就是我们经常提到的作用域链了。

作用域链

作用域链就是查找变量的这个一条链路。

那为什么 bar函数的 outer 会指向全局执行上下文,而不是 foo 的函数执行上下文呢?因为这个作用域链是有词法作用域决定的。

词法作用域

JS 中使用的是词法作用域。

词法作用域规定作用域是在函数定义时(代码编译阶段)的位置确定的,而不是执行时确定的。

所以 bar 函数虽然是在 foo中被执行的,但是 bar是在全局作用域下定义的,所以outer 指向了 全局执行上下文。

块级作用域中的变量查找

function bar() {
    var myName = "极客世界"
    let test1 = 100
    if (1) {
        let myName = "Chrome浏览器"
        console.log(test)
    }
}

function foo() {
    var myName = "极客邦"
    let test = 2
    {
        let test = 3
        bar()
    }
}

var myName = "极客时间"
let myAge = 10
let test = 1
foo()

img

这里,你肯定可以知道打印出来的是 1

闭包

闭包都是由哪些组成的呢

古典的闭包:从闭包最开始的来源:《The mechanical evaluation of expressions》中,可以看到闭包有两部分组成:

  • 环境部分
    • 环境
    • 标识符列表
  • 表达式部分

对应到 js 就是:

  • 环境部分
    • 环境 (函数的词法作用域,它是执行上下文中的一部分)
    • 标识符列表 (函数中用到的未声明的变量)
  • 表达式部分(函数表达式)
    image

结合词法作用域了解闭包

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这行代码时调用栈的情况,你可以参考下图:

img

根据词法作用域的规则,内部函数 getName 和 setName 总是可以访问它们的外部函数 foo 中的变量,所以当 innerBar 对象返回给全局变量 bar 时,虽然 foo 函数已经执行结束,但是 getName 和 setName 函数依然可以使用 foo 函数中的变量 myName 和 test1。所以当 foo 函数执行完成之后,其整个调用栈的状态如下图所示:

img

从上图可以看出,foo 函数执行完成之后,其执行上下文从栈顶弹出了,但是由于返回的 setName 和 getName 方法中使用了 foo 函数内部的变量 myName 和 test1,所以这两个变量依然保存在内存中。

所以什么是闭包呢?

出来当函数可以记住并且访问所在的词法作用域。就产生了闭包。就算函数是在当前的词法作用域之外执行。

最后补充一点,完整的图

完整的执行上下文包括了 「变量环境」「词法环境」「outer」「this」

image

文中没有提到 this,关于 this 的指向都已经知道了,就没有再记录了。向彻底知道 this 的话,要去翻看 ECMA 的文档了,每次都对 this 的定义做了修改

参考:

  • 极客时间 -> 浏览器与工作原理 -> 浏览器中的JavaScript执行机制
  • 《重学前端》 JavaScript执行(二):闭包和执行上下文到底是怎么回事?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant