09 闭包

Posted by CodingWithAlice on April 11, 2021

09 闭包

理论:

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

闭包的重点:在函数内部定义其他函数返回!!!不是调用外部作用于域块就能够直接形成的。

闭包优缺点:

  • 优点:① 能够读取函数内部的变量;②让这些变量一直存在于内存中,不会在调用结束后被垃圾回收机制回收;
  • 缺点:由于闭包会使用函数中的变量存在在内存中,内存消耗很大,所以不能滥用闭包;解决的办法是退出函数之前,将不使用的局部变量删除

产生闭包的核心:

  • 第一步是需要 预扫描内部函数
  • 第二步是把 内部函数引用的外部变量 保存到

案例:

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()

1、当 JavaScript 引擎执行到 foo() 时,首先会编译,并创建一个空 执行上下文 ,执行栈如下:

image-20210411105752559

​ 在编译过程中,遇到内部函数 setName,JavaScript引擎还要对内部函数做一次 快速的词法扫描,发现该内部函数引用了 foo 函数中的 myName 变量,由于是 内部函数引用了外部函数的变量,所以JavaScript引擎判断这是一个闭包,于是在 空间创建 一个 closure(foo) 的对象(这是一个内部对象,JavaScript是无法访问的) ,用来保存 myName 变量

​ 接着继续扫描到 getName 方法时,发现该函数内部还引用变量 test1,于是 JavaScript引擎又将 test1 添加到 closure(foo) 对象中

​ 由于 test2 并没有被内部函数引用,所以 test2 依然保存在调用栈中(图中 test2 应该在词法环境中)

image-20210411150011033

2、当 return innerBar 返回给全局变量 bar 时,虽然 foo 函数已经执行结束,但是 getNamesetName 函数依然可以使用 foo 函数中的变量 myNametest1。所以当 foo 函数执行完成之后,其整个调用栈的状态如下图所示:

image-20210411105909116

3、bar.setName 中给 myName 赋值时,调用栈如下图:

当前执行上下文–> foo 函数闭包 –> 全局执行上下文

image-20210411110043855

常见考题:

案例1:全部输出 3

// 下题并没有形成闭包
var data = [];
for (var i = 0; i < 3; i++) {
    data[i] = function () {
        console.log(i);
    };
}
data[0](); // 3
data[1](); // 3
data[2](); // 3

案例2:形成闭包

var data = [];
for (var i = 0; i < 3; i++) {
    data[i] = (function (i) {
        return function(){
            console.log(i);
        }
    })(i);
}

data[0](); // 0
data[1](); // 1
data[2](); // 2