闭包

0 阅读3分钟

作用域链

每一个执行上下文的变量环境中都存在一个outer指针,用来指向外部的执行上下文

当v8在查找一个变量时,会在当前执行上下文的变量环境中查找,如果没有,就去outer指针指向的执行上下文的变量环境中查找,直到找到,或者outer指针指向全局执行上下文(全局执行上下文的外部执行上下文是null)我们把这个查找的链条称作作用域链

function bar(){
    console.log(myName)//李四
}
function foo(){
    var myName = '张三'
    bar()
}
var myName = '李四'
foo()

当我们进行预编译,大概情况应该是这样的

09a868f796b744067ba5ca87731619e.png 但是当我们执行代码时,因为在bar函数中找不到myName,所以v8引擎会往上一级执行上下文中寻找,所以会来到全局执行上下文而不是foo执行上下文,所以最后输出的结果应该是李四

闭包

知道上面那个概念之后,闭包便很好理解了

闭包的出现源自下面两种规则:

  1. 一般情况下当一个执行上下文被执行完毕之后,便会被销毁
  2. 根据作用域的查找规则,内部函数一定可以访问外部函数中的变量

这两种情况被结合的后果就是:当一个函数被销毁的时候,但是它的子函数需要用到这些被销毁的变量时,就会被保存起来,但是依旧放在栈中,这些变量的集合就叫做闭包

你可能觉得我在叽里咕噜说些什么呢,没关系,我们找个简单点的例题来看一下

let count = 1
function main(){
    let count = 2
    function bar(){
        let count = 3
        function foo(){
         console.log(count)//3
        }
        foo()
    }
    bar()
}
main()

首先,对这段函数进行简单的预编译试试看

e220facdd44332f65894cdb6b01482d.png 当v8执行代码时,会从全局开始执行,一步步执行到foo的执行上下文中,但是因为有规则一的情况,bar的执行上下文和main的执行上下文应该被删除,这就导致foo在执行时,outer指针指不到foo的父级执行上下文bar中,那么就应该报错,但是因为规则二的情况,bar执行上下文就算被销毁,也会留下foo所需要的函数,放在闭包中,所以最后输出的应该是3

e071bcf781bf5c24c5234dcfa7740b8.png

众所周知,v8执行上下文的时候,会把执行上下文销毁,是为了给栈省空间,但是闭包的存在又让空间变得没法那么省,就会带来不好的情况,也就是爆栈,这就是闭包的缺点

那既然闭包有缺点,那么阴阳平衡,肯定就会有优点的,那就是可以定义私有模块,防止全局变量污染

比如:

let count = 0

function add() {
    count++
    return count
}

console.log(add());//1
console.log(add());//2
console.log(add());//3

这个时候,明明只是为了一个计数器,却在全局定义了一个count,万一下次要用定义count时,就会出问题,造成全局变量污染

这时候我们就要用闭包的特性,做一个计数器:

function bar() {
    let a=0
   return function () {
        a++
        return a
    }
}
var res=bar()
console.log(res())//1
console.log(res())//2
console.log(res())//3

这时候,当函数被执行完毕之时,变量a就会被销毁,防止全局变量污染