• 浅析js闭包


      闭包是一个老生常谈的问题,简单概括下闭包的形成的两个条件:

        1、定义在函数内部

        2、函数内部引用父层作用域变量

      举一个最简单的例子:

     1 function test() {
     2   var a = 1;
     3 
     4   function hello() {
     5      console.log(a);
     6   }
     7   return hello;
     8 }
     9 
    10  var world = test();
    11  world(); //1

      以上代码会在控制台输出“1”。这是什么为什么呢?函数内部变量在调用结束后一般都会销毁,以上代码在test方法调用结束后并没有被销毁,这是由于js语言本身垃圾回收导致的。众所周知,js的垃圾回收机制是引用计数,当变量在其他作用域被引用时,该变量就不会被销毁,会一直存在内存中,这样就形成了闭包。在调试代码的过程中也可以在开发者工具中看出,如下图所示:

      以上可以看出,当前环境存在一个作用域,Closure(text),包含引用父层的作用域的变量a。

      js函数参数传递分为数值传递和引用传递,数值传递针对参数的类型为number/string/boolean....;引用传递针对参数类型为object/array....;所以在针对引用父层参数时,要注意父层参数类型,如果为数值传递,父层参数改变并不会影响闭包执行结果,但是如果参数是引用传递,要注意闭包里的参数引用的是父层变量地址,变量发生改变,闭包的执行结果会随之改变,以下代码可以看出:

     1 var obj = {
     2             num: {
     3                 b: 1
     4             }
     5         }
     6 
     7         function test(obj) {
     8             var a = obj.num;
     9 
    10             function hello() {
    11                 console.log(a.b);
    12             }
    13             return hello;
    14         }
    15 
    16         var world = test(obj);
    17         world(); //1
    18         obj.num.b = 2;
    19         test(obj);
    20         world(); //2

      执行结果:

      再说一个经典的面试题:

    1 for (var i = 0; i < 5; i++) {
    2             setTimeout(function() {
    3                 console.log(i);
    4             }, 0);
    5         }

      想必大家都知道这段代码的执行结果是5个1,但是有没有想过为什么会是5个1呢?

      setTimeout函数比较特殊,其执行优先级要低于for循环,循环结束后才会被执行,其匿名函数类似于一个回调,匿名函数都引用了变量i,var声明的变量又不存在块级作用域,最终循环结束后,i值变为5,在回调执行过程中,会向父层作用域执行RHS查询(就是查找变量i的值是否存在),i的值目前存在于全局变量window中,但是已变成了5,所以会输出5个5。

      如何让其一次性输入0,1,2,3,4呢?

      其实问题的本源在于js语言中本来不存在块级作用域,for循环声明的变量i最终是在全局变量中,每次循环都会更新一次全局变量中的值,所以实现块级作用域有两种常用的方法,一是借助es6的声明变量的方法let,第二个是利用闭包;

      闭包的代码如下:

    1 for (var i = 0; i < 5; i++) {
    2             setTimeout((function(i) {
    3                 console.log(i);
    4             })(i), 0)
    5         }

      正如文章刚开始所述,setTimeout回调中引用的是父层函数中变量i的值,在每次声明的时候,都会将当前的i值传递给闭包,其有自己单独的作用域,不再是引用同一变量,具体作用域情况如下所示:

      以上仅为个人看法,如有错误,烦请指出,谢谢!

  • 相关阅读:
    JavaScript作用域学习笔记
    Object.prototype.toString.call() 区分对象类型
    oracle 经典SQL整理
    day31
    ID3决策树
    C# 中浅拷贝与深拷贝区别
    C#值类型和引用类型的区别
    C#守护进程(windows服务)
    C#线程池
    C#双缓冲绘图
  • 原文地址:https://www.cnblogs.com/gerry2019/p/10507344.html
Copyright © 2020-2023  润新知