zoukankan      html  css  js  c++  java
  • 闭包,隐式作用域,显示作用域

    首先来谈谈闭包,js经典问题了。解释也是众说纷纭,大同小异。这里引用阮一峰老师对它的简单解释:闭包是能够读取其他函数内部变量的函数。

    看一个例子:

    function foo() {
      var a = 2;
      function bar() {
         console.log( a );
      }
      return bar;
    }
    var baz = foo();
    baz(); // 2 

    这就是一个闭包函数,bar的执行能读取到foo中的变量a ,闭包与作用域息息相关。

    函数 bar() 的词法作用域能够访问 foo() 的内部作用域

    再看一个例子

    function foo() {
         var a = 2;
        function baz() {
             console.log( a ); // 2
        }
        bar( baz ); 
    }
    function bar(fn) {
        fn(); 
    }    
    

    bar函数的执行能取到foo中的变量,原理是fn就是baz而它又在foo内,可以通过作用域链往上查找到foo作用域下的变量a.baz就是中间的桥梁。

    var fn;
    function foo() {
        var a = 2;
        function baz() {
             console.log( a );
        }
        fn = baz; // 将 baz 分配给全局变量 
    } function bar() { fn(); // 妈妈快看呀,这就是闭包! } foo(); bar(); // 2

    上面这个例子,全局变量fn是个桥梁,连接了foo内部的baz函数,bar函数执行,fn函数执行,刚好fn函数就是foo内部的baz函数,这样就能取到baz的上级作用域foo中的a变量。

    闭包的使用场景:在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!

    闭包的优点;防止全局变量的冲突,缺点:变量始终保存在内存中,容易造成内存泄露。

    提到闭包,其实与作用域,作用域链是绑定在一起的。作用域又分为显示和隐式。然后又是对于闭包函数的执行又涉及到执行上下文,以及this(执行上下文中的一个属性) 关于this指向问题,可以看我另外的一篇博客,基本涉及到每一种情况。对于执行上下文,下一篇博客再写它。

    这里先看看作用域,看一个常见的例子!

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

    实际上输出结果是:5个6,因为延迟定时器的队列原因,即使是1000ms后第一个延迟函数执行,能获取到的i值是整个for循环之后的i值了。

    其实头疼的是不能挽留住每次循环的i值,所以想要得到理想的输出1,2,3,4,5.就需要考虑如何挽留的问题了。

    第一种:

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

    让循环中的延时器函数包裹在一个立即自执行的函数中,该函数中命名一个j变量,保存住j值。

    第二种:

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

    原理类似于上面的,但是是通过把i作为形参的方式传给自执行函数,然后再往里传递给延时函数。

    第三种:

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

    利用es6的let,生成块级作用域。相当于每次迭代都重新生成了。最常用的方式

    第四种 

    for (var i=1; i<=5; i++) {
            let j = i; // 是的,闭包的块作用域! setTimeout(
            function timer() {
                 console.log( j );
             }, j*1000 );
    }          

    循环中的i依然是var类型声明的,但是,在每次的循环中,声明一个j来接受该次循环的i值,并生成块级作用域。

    以上方法,第一种和第二种属于隐式作用域,并没有新的作用域生成,只是隐式的劫持了已存在的作用域,挽留住了稍纵即逝的i值。

    第三种和第四种方法利用了let的特性,let 声明会创建一个显示的作用域并与其进行 绑定。显式作用域不仅更加突出,在代码重构时也表现得更加健壮。在语法上,通过强制 性地将所有变量声明提升到块的顶部来产生更简洁的代码。这样更容易判断变量是否属于 某个作用域。

           

  • 相关阅读:
    mysql 7.5.8 服务无法启动 服务没有报告任何错误
    Ubuntu 16.04 php卸载
    函数式编程(3)-匿名函数
    函数式编程(2)-返回函数
    函数式编程(1)-高阶变成(3)-sorted
    函数式编程(1)-高阶变成(2)-filter
    函数式编程(1)-高阶变成(1)-map/reduce
    高级特性(4)-生成器
    高级特性(3)-列表生成式
    高级特性(2)-迭代
  • 原文地址:https://www.cnblogs.com/hjj2ldq/p/9266194.html
Copyright © 2011-2022 走看看