闭包,隐式作用域,显示作用域

首先来谈谈闭包,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 声明会创建一个显示的作用域并与其进行 绑定。显式作用域不仅更加突出,在代码重构时也表现得更加健壮。在语法上,通过强制 性地将所有变量声明提升到块的顶部来产生更简洁的代码。这样更容易判断变量是否属于 某个作用域。

       

猜你喜欢

转载自www.cnblogs.com/hjj2ldq/p/9266194.html