• 关于作用域闭包的一些理解


    闭包

    红宝书上对闭包的定义:有权访问另外一个函数作用域中变量的函数。

    MDN对闭包的定义是:是能够访问自由变量的函数。

    自由变量:是指在当前函数中可以使用的(但是既不是arguments也不是本函数定义的局部变量)。

    两个点:

    1. 是个函数
    2. 能访问另一个函数作用域中的变量,即使外层函数的上下文已经被销毁

    就是说我们常见的比如内部函数从外部函数返回这种状态,该内部函数就是闭包。可以看如下特性中的示例!

    说明闭包的几个特性:

    • 可以访问当前函数以外的变量
    • function outer() {
                  var date = '11月1日';
                  function inner(str) {
                      console.log(str + date)
                  }
                  return inner('today is ')
              }
              outer()
      
              function outer() {
                  var date = '11月1日';
                  return function () {
                      console.log('today is ' + date)
                  }()
              }
              outer()   // 上下两例均返回“today is 11月1日”
    • 即使外部函数已经返回,闭包仍然能够访问外部定义的变量
    • function outer() {
                  var date = '11月1日';
                  function inner() {
                      console.log('today is ' + date)
                  }
                  return inner
              }
      
              // 以下是拆成分步执行,实际等同于outer()();
      // 先执行outer()得到一个返回值inner,此时outer函数执行完毕,跳出outer这个外层函数
      // 然后执行inner(),但是此时依然可用outer定义的变量date var getDate = outer() getDate()
    • 闭包可以修改外部函数的变量的值
    • function outer() {
                  var date = '11月1日';
                  function inner(newDate) {
                      date = newDate   // 将传入的值替换掉外层的date
                      console.log('today is ' + date)
                  }
                  return inner
              }
      
              var getDate = outer()
              getDate('191101')   // “today is 191101”

    闭包的作用域链:

    以下例分析:

            var scope = 'global';
            function checkscope() {
                var scope = 'local';
                function fun() {
                    return scope;
                }
                return fun;
            }
    
            var check = checkscope();
            console.log(check());   // 'local'

    执行过程:

    1. 进入全局代码,创建全局执行上下文并压入执行上下文栈
    2. 全局上下文初始化
    3. 执行checkscope函数,创建checkscope执行上下文并将其压入执行栈
    4. checkscope函数上下文初始化,创建变量对象、作用域链、this
    5. checkscope函数执行,执行完后checkscope执行上下文从执行栈中弹出
    6. 由于checkscope函数返回了一个f函数,因此创建f()执行上下文,将fun()的执行上下文压入执行栈,然后执行fun(),同样的,创建变量对象、作用域链、this
    7. fun()执行完毕后,从执行栈中弹出。

    这个流程可以看到,checkscope执行完毕后是带着返回值弹出了执行栈的,在fun执行的时候checkscope函数的上下文已经被销毁了,但是,函数fun执行上下文维护了一个作用域链,结构如下:

    funContext = {
        Scope: [AO, checkscopeContext.AO, globalContext.VO],
    }

    指向关系是:当前作用域 -> checkscope -> 全局,不论checkscope 是否被销毁,fun函数都可以通过fun的作用域链找到它,这是闭包实现的关键

    有关全局环境下函数嵌套与非嵌套时作用域链的指向分析,参考:深入浅出图解作用域链和闭包

    一些常见的闭包考题

    题1:

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

    答案很显然:3、3、3

    上面这个题的分析参照我的另一篇博客分析:let和const,里面详细的分析了这个题的过程~

    如何才能让这个题输出我们想要的0、1、2呢?

    解法一:博客当中给出了使用let的写法,很简单只需要将for中的var替换成let即可

    解法二:

    还有别的方法吗?本片将采用闭包的方式解决这个问题~~~回想一下闭包的状态是什么?内层函数可以使用外层函数定义的变量呀!

    所以第一步:我们在function中return一个新的函数,在内层函数中访问变量 i。

    然后考虑我们如何才能把当前的i传到外层function中呢?立刻我们联想到利用参数!

    第二步:外层函数自执行,将 i 作为参数传入外层的function中

    因此得到如下优化后的代码:

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

    结果为:0、1、2

    正是我们要的结果啦~

    解法三:

    这里还可以改成我们常见的定时器写法

            for (var i = 0; i < 3; i++) {
                (function (i) {
                    setTimeout(function () {
                        console.log(i)
                    }, 100 * i)
                })(i)
            }

    解法三其实和解法二的本质相同,都是将变量 i 的值复制给外层function的参数 i ,在函数内部又创建一个用于访问 i 的匿名函数,这样每个函数都有一个 i 的副本,就不会相互影响!

    题2:这两段代码在checkscope执行完后,f所引用的自由变量scope会被垃圾回收吗?why?

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

    结论是:第一个代码段中的scope特定时间后会被回收,第二段代码的自由变量不会回收

    分析:

    现在主流浏览器的垃圾回收算法是:标记清除。当垃圾回收开始时,从root开始寻找这个对象的引用是否可达,也就是找是否存在相互引用,如果引用链断裂,那么这个对象就可以被回收!

    对于第一段代码,checkscope()执行完毕后被弹出执行栈,并且也没有其他引用,Root开始查找时不可达,因此闭包引用的自由变量scope过段时间可以被回收

    对于第二段代码,由于var foo = checkscope(),checkscope()执行完成后,将foo()执行上下文压入执行栈,foo()指向堆中的自由变量 f ,对于Root来说可达,因此不会被回收!!

    如果想要scope一定可以被回收,只要加:foo = null;即可!

  • 相关阅读:
    【noip模拟赛10】奇怪的贸易 高精度
    【noip模拟赛8】魔术棋子
    【noip模拟赛7】足球比赛 树
    P2502 [HAOI2006]旅行 并查集
    python发邮件:
    读取excel表格.py
    allure的其他参数
    生成allure测试报告:
    Java
    调用阿里云接口实现短信消息的发送源码——CSDN博客
  • 原文地址:https://www.cnblogs.com/ningyn0712/p/11723872.html
Copyright © 2020-2023  润新知