• 技术文章 >web前端 >js教程

    深入了解JavaScript中的闭包

    PHPzhongPHPzhong2021-05-31 15:05:14转载798
    本篇文章给大家介绍一下JavaScript中的闭包。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

    闭包——非常重要但又难以掌握的概念,理解闭包可以看作是某种意义上的重生——《你不知道的Js》
    虽然关于闭包,虽然大家可能已经看腻了,但我仍要试着去总结下它!!!

    一、什么是闭包

    顾名思义,遇见问题先问为什么是我们一贯的思维方式,我们尝试回答一下:

    我们试着用代码来描述一下上面的回答,看看你最中意哪一个~

    1.1 闭包是函数内部的子函数

    先看这段代码:

    function foo(params) {
        var a = '余光';
    
        function bar() {
            console.log(a);
        }
        bar()
    }
    
    foo(); // 余光

    基于词法作用域的查找规则,bar函数可以成功的打印a变量,并且它也是foo的子函数,但严格来说它并没有清晰的表达出闭包这一概念,说它表达的是嵌套函数可以访问声明于大外部作用域的变量更准确一些。

    1.2 闭包就是能够读取其他函数内部变量的函数,在本质上是函数内部和函数外部链接的桥梁

    再来看下面的例子:

    function foo(params) {
        var a = '余光';
    
        function bar() {
            console.log(a);
        }
        return bar;
    }
    
    var res = foo();
    res(); // 余光

    结果一致,这是因为此时res是执行foo函数时返回的bar引用,bar函数得以保存了它饿词法环境。

    1.3 函数和对其周围状态(词法环境)的引用捆绑在一起构成闭包(closure)

    我们来看下面代码:

    var name = '余光';
    
    function foo() {
      console.log(name); // 余光 
    }
    
    foo(); //余光

    foo的上下文被静态的保存了下来,而且是在该函数创建的时候就保存了。下面我们来验证一下:

    var name = '余光';
    
    function foo() {
      console.log(name); // 余光
    }
    
    (function (func) {
        var name = '老王';
    
        func()
    })(foo); // 余光

    这里我们就可以理解——函数被创建后就形成了闭包,他们保存了上层上下文的作用域链,并且保存在[[scope]]中,如果你对[[scope]]的概念已经模糊了,不妨花几分钟看看《JavaScript中的执行上下文》这篇文章。

    1.4 总结

    注意:闭包是函数内部的返回的子函数这句话本身没错,但要看从什么角度出发:

    ECMAScript中,闭包指的是:

    总结:

    注意:这些并不是闭包的全部,就好像当你被问到——闭包是什么的时候,你的上述回答并不能结束这个话题,往往会引申出更多的话题。

    在这里插入图片描述

    二、尝试分析闭包

    还是那段经典代码:

    var scope = "global scope";
    function checkscope(){
        var scope = "local scope";
        function f(){
            return scope;
        }
        return f;
    }
    
    var foo = checkscope();
    foo(); // local scope

    首先我们要分析一下这段代码中执行上下文栈和执行上下文的变化情况。

    在这里插入图片描述

    f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?

    当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:

    因为这个作用域链:

    多么浪漫的思想——只要你需要我,那我我本应该被销毁,你也能找到我~

    在这里插入图片描述

    三、经典问题

    3.1 多个对象引用同一个[[Scope]],你遇到过吗?

    直接上代码:

    var child1;
    var child2;
    function parent() {
        var x = 1;
    
        child1 = function () {
            console.log(++x)
        };
        child2 = function () {
            console.log(--x)
        };
    }
    parent();
    child1(); // 2
    child1(); // 3
    child2(); // 2

    大家可能不理解,child1child他们两个函数在创建后都保存了上层上下文,万万没想到,同一个上下文创建的闭包是共用一个[[scope]]属性的,某个闭包对其中[[Scope]]的变量做修改会影响到其他闭包对其变量的读取。

    3.2 闭包轻松解决的经典问题

    大家一定对下面这段代码很眼熟:

    var arr = []
    for(var i = 0; i < 10; i++){
        arr[i] = function () {
            console.log(i)
        }
    }
    arr[0](); // 10
    arr[1](); // 10
    arr[2](); // 10
    arr[3](); // 10

    我们这么解释它:同一个上下文中创建的闭包是共用一个[[Scope]]属性的

    因此上层上下文中的变量i是可以很容易就被改变的。

    arr[0],arr[1]…arr[9]他们共用一个[[scope]],最终执行的时候结果当然一样。

    如何利用闭包来解决这个问题呢?

    var arr = []
    for(var i = 0; i < 10; i++){
        arr[i] = (function (i) {
            return function () {
                console.log(i);
            }
        })(i)
    }
    arr[0](); // 0
    arr[1](); // 1
    arr[2](); // 2
    arr[3](); // 3

    我们通过立即执行匿名函数的方式隔离了作用域,当执行 arr[0] 函数的时候,arr[0] 函数的作用域链发生了改变:

    arr[0]Context = {
        Scope: [AO, 匿名函数Context.AO globalContext.VO]
    }

    匿名函数执行上下文的AO为:

    匿名函数Context = {
        AO: {
            arguments: {
                0: 0,
                length: 1
            },
            i: 0
        }
    }

    我们看到,这时函数的[[Scope]]属性就有了真正想要的值了,为了达到这样的目的,我们不得不在[[Scope]]中创建额外的变量对象。要注意的是,在返回的函数中,如果要获取i的值,那么该值还是会是10。

    3.3 总结

    推荐学习:《PHP视频教程

    声明:本文转载于:csdn,如有侵犯,请联系admin@php.cn删除
    专题推荐:JavaScript 闭包
    上一篇:Nodejs极简入门教程(二):定时器_node.js 下一篇:JavaScript中的迭代器和生成器详解_javascript技巧
    大前端线上培训班

    相关文章推荐

    • javascript如何将数组转换为字符串• javascript字符串方法有哪些• JavaScript中解析parseInt()的怪异行为• JavaScript的整除符号是什么• 聊聊JavaScript中eval()函数的用法• 浅谈JavaScript中的事件委托

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网