likes
comments
collection
share

[闭包]原来不过如此!👌

作者站长头像
站长
· 阅读数 28

在学习闭包之前,总听说这一块内容大家都很难理解,称作最让人费解的一环。可在对闭包的学习展开后,原谅我说一句,闭包徒有虚名!😎现在就一起看看该如何顺利有效地把它拿下吧。

1. 作用域链

之前我们聊过和作用域相关的内容,所以大家对它都有了一定的了解。现在我们来认识什么是作用域链。看下面这些简单的代码,你认为返回的结果会是什么呢?

function a() {
    console.log('n =' + n);
}

function b() {
    var n = 2
    a()
}

var n = 1

b()

按照我们之前讲的作用域来看,可以做出如下的图示,分析过程是这样的:

    1. 全局作用域进入了调用栈中,在全局中声明了函数a和函数b以及一个变量n。
    1. 全局预编译结束后开始执行,将n赋值为1,接着调用了函数b,开始了函数b的预编译。
    1. 函数b作用域进入栈中,声明了变量n。开始执行函数b,将n重新赋值为2,调用了函数a。
    1. 然后函数a作用域进入栈中,什么都没有,开始执行,输出变量n。
    1. 首先在函数a中找变量n的声明,发现没有,就来到下一级b作用域中。
    1. 在b的变量环境中找到n = 2,输出n,代码执行结束。
[闭包]原来不过如此!👌

按理说应该是输出n = 2对吧,但其实不然,最后得出的输出是n = 1。我想大家会觉得很奇怪,这是因为什么,答案就是作用域链起到了作用。

1.1 作用域链

作用域链(Scope Chain)是JavaScript中一个核心的概念,用于确定在执行代码时如何访问变量、函数以及对象。它是执行上下文中所有嵌套作用域的一个链式列表,这个链帮助解释器确定变量的访问权限和查找变量的路径。

其实每个作用域里的变量环境中都会有个outer,它在作用域链中起到至关重要的作用。回到上面这个案例,当调用函数a想要输出n时,在本作用域没有找到变量n的声明,所以根据outer的指示来到全局作用域中,最终找到n = 1。可是为什么函数a作用域中的outer不指向函数b作用域中的outer呢?🤔

1.2 词法作用域

这里就要提到词法作用域了,简单来说,就是作用域所处的位置,而一个域所处的环境,是由函数声明的位置决定的。因为函数a和函数b都是在全局中声明的,所以它们的outer都指向全局中的outer,全局中的outer则是指向null,毕竟它是最外层作用域了嘛。

2. 闭包

讲完了什么是作用域链,想必对代码的预编译和执行有了更加深刻的理解。那让我们接着分析下面的案例,看看最后的输出会是什么吧!

let globalVar = "I'm global";

function outerFunc() {
    let outerVar = "I'm outside";

    function innerFunc() {
        let innerVar = "I'm inside";
        
        console.log(globalVar);  
        console.log(outerVar);  
        console.log('innerVar);  

       
        globalVar = "Changed globally";
    }

    innerFunc();
    
    console.log(globalVar);  
}

outerFunc();

console.log(globalVar);

看完代码,冷静分析预编译和执行的过程,做出图示。在这个例子中,有三个作用域级别:

  • 全局作用域,包含变量globalVar。
  • 函数outerFunc的作用域,包含变量outerVar。
  • 函数innerFunc的作用域,是最内层,包含变量innerVar。

[闭包]原来不过如此!👌

    1. 当innerFunc执行时,它首先在其自身作用域寻找变量,没有找到global,就会沿着作用域链向外搜索,最终来到外层全局作用域中找到了globalVar = "I'm global"。
    1. 接着找变量outerVar的声明,在outerFunc的作用域中找到了outerVar = "I'm outside"。
    1. 最后找变量innerVar,在自身的作用域中找到innerVar = "I'm inside"。
    1. 因为在innerFunc中globalVar被重新赋值,所以后面输出的都是"Changed globally"。

现在我们对作用域链已经是了解了,发现原理很简单,很容易令人接受的对吧。如果对这个案例稍加改变呢?让我们一起看看情况如何。

let globalVar = "I'm global";

function outerFunc() {
    let outerVar = "I'm outside";

    function innerFunc() {
        let innerVar = "I'm inside";
        
        console.log(globalVar);   
        console.log(outerVar);   
        console.log(innerVar);   

        globalVar = "Changed globally";
    }

    return innerFunc;
    
}

var outerVar = "Changed outerVar"
const foo = outerFunc();
foo()
 

基本的分析过程是差不多的,这里的区别在于没有在outerFunc中执行innerFunc,而是将innerFunc作为结果返回出去给变量foo了。这样做的话会有一种情况发生,当执行到return innerFunc;这一句时,outerFunc已经全部执行完毕了,根据调用栈的规则,里面的outerFunc作用域应该立即被销毁,那接下来执行foo其实也就是innerFunc,按理是找不到变量outerVar的声明了,那它会继续向下来到全局作用域中找到outerVar = "Changed outerVar"嘛?结果是不会,最后的输出和上面的案例是一样的,可是这是为什么呢?

[闭包]原来不过如此!👌

因为当outerFunc作用域被销毁时,在调用栈中的同一个位置会留下一个“背包”,里面装的就是innerFunc需要用到的变量。而这个“背包”也被称为闭包。

根据js词法作用域的规则,内部函数总能访问外部函数中的变量,当通过调用一个外部函数返回的一个内部函数后,即使外部函数执行已经结束了,但是内部函数引用了外部函数中的变量也依旧需要被保存在内存中,我们把这些变量的集合称为闭包

看完是不是觉得恍然大悟,原来一直认为很难得[闭包]好像也不难呢,那么恭喜你,对JavaScript中的难点已经把握了,相信你对js的学习一定是手到擒来!让我们一起加油努力吧!!✌️

转载自:https://juejin.cn/post/7372577541112840204
评论
请登录