5分钟懂一个前端知识点系列之作用域链与闭包

5分钟懂一个前端知识点系列之作用域链与闭包

码农世界 2024-05-31 前端 82 次浏览 0个评论

前言

来了来了,它来了!五分钟系列它又来了!

先来简单引用一下官方简介,大家看看简单看看就行。

作用域链

在JavaScript中,作用域链是用来解析变量和函数引用的一个机制。每个函数在执行时都会有一个与之关联的作用域链,这个链条由一系列的作用域(作用域对象)组成,从当前函数的局部作用域开始,向外依次延伸至包含它的函数作用域,直至全局作用域。当需要访问一个变量时,JavaScript引擎会沿着作用域链逐级向上查找,直到找到该变量为止,或者查找不到导致引用错误。

闭包

闭包是JavaScript中一个重要的概念,它使得内部函数可以访问其外部函数作用域中的变量,即使外部函数已经执行完毕。闭包的形成发生在当一个内部函数引用了其外部函数的变量,并且这个内部函数在外部函数执行完毕后仍然可以被访问到时。

作用域链的产生与详解

我们都知道在函数和全局进行编译之前,JS都会先进行预编译,这里如果不知道什么是预编译的小伙伴建议先看看我的之前的五分钟系列文章:5分钟懂一个前端知识点系列之预编译 - 掘金 (juejin.cn)

在JS执行预编译的时候,会进行变量和函数提升,执行一个上下文的对象。上下文对象中会有一个环境叫做词法环境。它包含了变量环境以及该环境中声明的函数可以访问的所有外部变量和函数outer作用域也叫词法作用域。作用域链就是由outer指向所产生的一个指向链。



每次当寻找一个变量的时候都会在上下文对象中寻找,如果没有则通过outer指向的外层作用域里面寻找直到找到全局对象。那么每次寻找的外层作用域到底是哪一层呢?看看下面的这段代码:

    
    function fn1(){
        var num =1;
        fn2();
    }
    function fn2(){
        console.log(num);
    }
    var num =2;
    fn1();

这段代码你认为的执行结果是什么?

如果你认为的是1,函数在哪被调用,那么调用它的那层上下文对象就是函数的外层作用域那就错啦!

一个函数的外层作用域在函数被声明的时候就确定了!

那这是什么意思呢?意思就是你的函数写在哪里外层作用域就是你写的地方(预编译:函数声明),

所以上面的代码中调用fn2()执行的结果是2而不是1。

闭包

闭包的作用

那么讲完作用域链之后,再来讲讲闭包。先来看看这段代码:

for (var i = 0; i < 10; i++) {
    arr[i] = function () {
        console.log(i);
    }
}
arr.forEach(function (item) {
    item();
})

我可以先告诉你,上面的代码打印的全部都是10。因为在arr[i]赋值的时候,function(){console.log(i);}并没有执行,在for循环结束之后,i=10,再次调用arr[i]()打印的i值将会都是10而不是从0-9。

可以先想想结束的时候可以再回头看看自己能不能解决。

闭包的概念与产生
  • 闭包:

    外部函数定义了内部函数并且内部函数里面引用了外部函数的变量就会有闭包的形成

    内存管理机制,当一个方法执行完之后。就会对这个方法开辟的内存进行回收,而当内部函数调用了外部函数时,外部函数已经执行完毕,内存空间已经被回收。这样内部函数就无法找到外部函数中的变量,那么闭包就是这样产生的。用来存储内部函数对外部函数引用的变量与方法。

    闭包的形成发生在函数声明的时候,而不是函数被调用的时候。

    下面是举的一个例子详解:

    -----------------------------------------------------------------------------
    function fn1(){
        var count =1;
        var num =2;
        function fn2(){
            count++;
            console.log(count);
        }
        return fn2;
    }
    var fn =fn1();
    fn();
    fn();
    fn();
    -----------------------------------------------------------------------------
    首先JS编译器进行预编译:
    1-创建一个全局上下文对象叫GO
    GO {
    }
    2-变量提升
    GO {
        fn : undefined
    }
    3-函数提升
    GO {
        fn: undefined,
        fn1: function fn1(){}
    }
    预编译结束,开始执行代码
    执行到fn =fn1()时,开始对函数fn1()进行预编译
    1- 创建一个函数上下文对象叫AO
    AO{
    }
    2- 变量提升
    AO {
        count :undefined,
        num :undefined
    }
    3- 形参赋值,没有直接跳过
    4- 函数提升
    AO {
         count :undefined,
         num :undefined,
         fn2 :function fn2(){}
    }
    检测到fn2有对fn1进行变量引用则创建闭包叫做BO
    BO {
        count :0011XX(这里存的是AO对象中count值存储的地址)
    }
    函数预编译执行完毕,进行函数代码执行,执行赋值操作
    AO {
        count :1,
        num :2,
        fn2 :function fn2(){}
    }
    最后进行return,函数执行完毕,上下文对象AO进行销毁。留下闭包BO
    执行fn()时,fn2所调用的count就是BO中的count,所以打印的是2,3,4
    

    怎么样,有了解了闭包的形成和使用吗?如果了解的话可以回去写那道题目啦!

    总结

    • 闭包优点:实现封装和模块化,允许数据隐藏,支持函数式编程特性,如柯里化和偏应用。保持状态,适用于计数器、缓存等场景。

    • 闭包缺点:可能导致内存泄漏,未及时释放不再使用的资源;过度使用可能引起理解难度增加,影响代码可读性。

      所以大家在平常使用的时候,要注意不要过度使用闭包哦。使用可以但不要过量哦。

      好了本次5分钟系列到此结束!谢谢大家。喜欢的话点个关注吧,内容持续更新中。。。

转载请注明来自码农世界,本文标题:《5分钟懂一个前端知识点系列之作用域链与闭包》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,82人围观)参与讨论

还没有评论,来说两句吧...

Top