阅读 106

词法作用域、作用域链、闭包

词法作用域是指作用域是由代码中函数声明的位置决定的。和函数的调用位置没有关系

一、作用域链

作用域有全局作用域、函数作用域、块级作用域三种。

每个执行上下文(除了全局执行上下文)都有一个outer,该值指向外部的执行上下文。

例子:

 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函数,bar=undefied放入变量环境,并压入调用栈。foo函数的outer属性指向全局执行上下文。

执行:

  • 执行到var bar =  foo()时,编译foo函数。

  • 创建foo函数的执行上下文,myName、innerBar为undefied放入foo函数的变量环境中,foo函数的执行上下文入栈。

  • test1、test2为undefied放入foo函数的词法环境中。

  • 执行foo函数,myName、innerBar、test1、test2赋值,foo函数的执行上下文出栈。

  • bar被赋值为innerBar的地址引用。

  • 执行bar.setName("极客邦"),编译setName函数。

  • 创建setName函数的执行上下文,outer指向foo函数(根据定义的位置)执行上下文入栈。

  • 执行setName函数,查找myName,setName函数中没有,向outer所指向的foo函数查找,找到了赋值,setName函数的执行上下文出栈。

  • 执行bar.getName(),编译getName函数。

  • 创建getName的执行上下文,并入栈,outer指向foo函数(根据定义的位置)。

  • 执行console.log(test1),getName中没有,向outer指向的foo函数中查找,找到赋值,getName的执行上下文出栈。

setName的作用域链:setName --> foo ---> window对象。

getName的作用域链:getName --> foo ---> window对象。

二、闭包

上面的例子中,foo函数的执行完了,为什么在getName、setName中获取到foo函数中的变量?这就是我们要讲的闭包。

闭包,当一个外部函数调用返回一个内部函数时,我们能通过返回的这个内部函数访问外部函数中的变量,能访问的这些变量的集合就叫做闭包。比如上面例子中,test1、myName的集合就可以成为foo函数的闭包。

那么为什么foo函数已经执行完了,执行上下文已经出栈了,setName、getName中还能获取到foo函数中的变量?因为虽然foo函数已经执行完了,执行上下文出栈了,但是test1、myName函数因为在setName、getName中被使用了,所以这两个变量还在内存中,但是只能这两个函数访问得到。

 function foo() {
    var myName = "极客时间"
    let test1 = 1
    const test2 = 2
    var innerBar = { 
        setName:function(newName){
            myName = newName
        },
        getName:function(){
            console.log(test1)
            return myName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())
复制代码

如上面这个例子:

当js引擎在编译foo函数时,快速的进行词法扫描,发现foo函数中的myName、test1变量被内部函数使用了,于是在堆空间中创建了一个对象closure(foo),并将这两个对象放入该对象中。

产生闭包的核心:

  • 预扫描内部函数
  • 将内部函数使用到的外部变量保存到堆中。

注意:

  • 如果引用闭包函数的是一个局部变量,那么在这个变量所属的函数执行完之后,闭包就会被回收。
  • 如果引用必报函数的是一个全局变量,那么只有当页面被关闭时才会被回收。
  • 只有当函数里面定义内部函数,内部函数访问外部函数时,才会产生闭包。
文章分类
前端
文章标签