美文网首页
作用域、作用域链和闭包那些事

作用域、作用域链和闭包那些事

作者: 前端小学生_f675 | 来源:发表于2018-11-12 10:08 被阅读0次

    之所以写这篇文章,是跟我经历有关,前两天面试碰到一个很无理的面试官,年纪不大,电话面试,说话傲气,自称是写react和ts的。问道了闭包的概念,然后我回答闭包是前端比较容易混淆的概念,而且他的概念很多,阮一峰的定义是:。。。然后就让我跳过去了。。。

    额。。。闭包概念多不多,下面我将进行逐次举例:

    一、概念:先从带我入门的阮一峰大神开始吧:

    1,阮一峰:我的理解是,闭包就是能够读取其他函数内部变量的函数。

    2,廖雪峰

    image

    廖大神没有给明定义而是举了一个例子,简单概括来说:

    即在外部函数内定义内部函数,当把内部函数返回时相关的参数和变量都会被保存在返回函数中。

    3,MDN:闭包是函数和声明该函数的词法环境的组合。

    4,红宝书(JS高级程序设计):有权访问另一个函数作用域中变量的函数。

    5,某简书作者(思路非常有意思):

    闭包是一种特殊的对象。

    它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。

    当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。

    在大多数理解中,包括许多著名的书籍,文章里都以函数B的名字代指这里生成的闭包。而在chrome中,则以执行上下文A的函数名代指闭包。

    综上所述:概念真的很多。。。

    但是概念多并不妨碍我们去理解闭包,先从作用域、作用域链开始解释吧:

    二、作用域(scope):

    变量作用域分为两种:全局变量和局部变量(函数作用域)

    这两个概念前端都知道我不做多余解释。

    三、作用域链(chain scope):

    这里面涉及的概念有点多了:

    1>基础数据类型与引用数据类型

    2>内存空间

    3>垃圾回收机制

    4>执行上下文

    5>变量对象与活动对象

    但是可以通过一张图轻松看明白:

    image

    作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

    简单来说就是:

    当前执行的代码所在环境的变量对象(如果该环境是函数,则将其活动对象作为变量对象),下一个变量对象来自包含环境(包含当前执行环境的环境),下一个变量对象来自包含环境的环境,依次往上,直到全局执行环境的变量对象。全局执行环境的变量对象始终是作用域链中的最后一个对象。

    标识符解析是沿着作用域一级一级的向上搜索标识符的过程。搜索过程始终是从作用域的前端逐地向后回溯,直到找到标识符(找不到,就会导致错误发生)。

    作用域链决定了全局变量和局部变量(函数变量)的有限访问权。

    看图说话:

    image

    这里还要引出另外两个概念:

    四、提升:

    1,变量提升:

    看一段代码:

    var name = "haha";
    
    function changeName(){
    
           console.log(name)
    
           var name = "xixi";
    }
    
    changeName();
    
    console.log(name);
    

    输出结果是:undefined haha

    为什么不是: haha或者xixi啊?

    这个现象就是变量提升,就是把变量提升到函数的顶部,需要注意的是,变量提升只是提升变量的声明,不会吧变量的值也提升上来!

    上述代码相当于:

    var name="haha";
    
    function changeName(){
    
         var name;
    
         console.log(name);
    
         name="xixi";
    
    }
    
     changeName();
    
    console.log(name);
    

    2,函数提升:

    在JavaScript中函数的创建方式有三种:函数声明(静态的,像函数example()的形式)、函数表达式(函数字面量)、函数构造法(动态的,匿名的)。

    注意:只有函数声明才存在提升。

    **//函数声明
    function myTest1(){
    
        func();
    
        function func(){
    
            console.log("我可以被提升");
    
        }
    
    }
    
    myTest1();//函数表达式function myTest2(){
    
        func();
    
        var func =function(){
    
            console.log("我不能被提升");
    
        }
    
    }
    
    myTest2();
    

    打印结果:

    image

    3,函数提升和变量提升的优先级:
    函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖。

    示例:

    console.log(a);// f a() {console.log(10)}
    
    console.log(a());// undefined
    
    var a =3;
    
    function a(){
    
            console.log(10)//10
    
    }
    
    console.log(a)//3
    
    a =6;
    
    console.log(a());//a is not a function;
    

    五、下面回到闭包的用途:

    1,读取函数内部变量(函数作为返回值):

    用法:通过函数返回值:

    function f1(){
    
        var n=999;
    
          nAdd=function(){n+=1}
    
            function f2(){
    
                     alert(n);
    
               }
    
            return f2;
    
      }
    
      var result=f1();
    
      result(); // 999
    
      nAdd();
    
      result(); // 1000
    

    result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

    2,模拟一个块级作用域(函数作为参数被传递):

    for(var i=1; i<=5; i++) {
    
            (function(i){
    
                 setTimeout(function timer(){
    
                 console.log(i);
    
             }, i*1000);
    
          })(i)
    
    }
    

    3,模拟私有方法(模块模式):

    (function(){
    
        var a =10;
    
        var b =20;
    
            function add(num1, num2){
    
                 var num1 = !!num1 ? num1 : a;
    
                 var num2 = !!num2 ? num2 : b;
    
                 return num1 + num2;
    
              }
    
    window.add = add;
    
    })();
    
    add(10,20);
    

    六、闭包的危害:

    关于闭包的危害,倒是没什么可争论的:

    如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。(MDN)

    阮一峰:
    1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
    ----------------一个朋友跟我说父函数看不懂,可以理解成函数外部的。

    PS:

    作为一个老司机,还是忍不住BB两句,那个自称会写TS的前端小朋友,谦虚点没什么坏处,前端的技术更新很快,但是对经典的学习,是每个开发者必须要走完的路!!!

    前端水很深,且行且珍惜,保持一颗学习心,一颗愿意交流的心,比什么都重要!

    Be Gentle! && Stay Hungry Stay Foolish!

    PPS:

    简书附上代码还是第一次,如果引起不适,请多担待,听朋友的话,把富文本改成了Markdown。一时还不太习惯,以后慢慢改进好了!

    最后附上学习链接:

    廖雪峰官网:

    https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/00143449934543461c9d5dfeeb848f5b72bd012e1113d15000

    阮一峰:

    http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

    MDN:

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

    某简书作者:

    https://www.jianshu.com/p/21a16d44f150

    其他:
    https://blog.csdn.net/whd526/article/details/70990994

    https://www.cnblogs.com/wangfupeng1988/p/3994065.html

    https://www.cnblogs.com/oxiaojiano/p/7918967.html

    https://www.cnblogs.com/buchongming/p/5858026.html

    相关文章

      网友评论

          本文标题:作用域、作用域链和闭包那些事

          本文链接:https://www.haomeiwen.com/subject/wfjqfqtx.html