关于闭包的一点理解

说闭包之前我们要先粗略的讨论一下作用域

每一行代码在运行时都会有一个作用域,在《你不知道的javascript》里是这样解释作用域的 "一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量。 这套规则被称为作用域"。简单说就是里面存放着当前能访问到的变量,那这些变量是以什么形式储存呢,查找的时候又是如何查找的呢?js里有一个作用域链,当需要用到变量时,他会沿着作用域链一级一级的向下找,直到找到变量或者到达最外层(全局变量)才会停止,我们用代码来说明。

function foo(a) {   
  console.log( a + b ); 
} 
 
var b = 2; 
 
foo( 2 ); //  4

当执行到var b = 2这一行时 作用域链中只包含了全局变量,当执行到foo函数内部时; 会先复制一份父级的作用域链,在向其前端推入当前的变量对象

b = undefined   

 

var b = 2时的作用域链  

 

a = 2
b = 2

 

 

执行到foo函数内部时的作用域链

我们再来讨论一下js的块级作用域

js中函数可以产生作用域,当我们需要作用域时,不想新建全局变量污染环境是,我们就可以创建一个函数并执行他,而我们执行函数时往往通过需要调用函数名称,如果我们只是向单纯的创建一个作用域,函数名字函数名字也会污染环境,这时我们就需要立即执行函数来帮助我们,即创建一个匿名函数并立即调用他,看例子

//普通函数
function fn(a){
 console.log(a)
}
fn(2)


//立即执行函数
(function (a){
 console.log(a)
})(2)

 

由于function(){}()这样的写法会产生错误,所以立即执行函数需要用()将匿名函数包起来,(function(){})()这样就是立即执行函数的样子,和普通函数一样也可以进行传参

 

 

下面我们进入主题,说一下闭包

闭包可以使我们跨作用域访问变量,其中的一种表现就是在函数外部可以访问到属于函数内部的变量,我们来看一个例子

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

 

这就是一个最简单的闭包,本来变量a时函数foo的内部变量,正常情况下时无法访问的但是我们可以在其内部再写一个函数,获得变量a再将这个内部函数返回,即可获得这个内部变量,当然这只是为了演示闭包,这个函数没有实际意义。

我们再来看一下闭包的其他表现形式,最经典的for循环

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
             console.log(i) ;
        });
}
return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2];
f1();
f2();
f3();

 

count函数会返回一个数组,数组中有三个元素,分别是三个函数,根据期望f1(), f2(), f3(), 应该分别打印 1,2,3,但是结果却是打印的三个4

我们来分析一下,在for循环里向数组push了匿名函数,而这个匿名函数中使用了i这个外部变量,也就是说在匿名函数的作用域链中有一个变量i,当for循环执行完毕之后i变成了4,当我们调用f1()时也就是调用了匿名函数,此时函数要去查找i,找到作用域链中的i,发现此时i是4,同理f2(), f3()一样都会打印4。

为了印证匿名函数中作用域链的i是随着for循环在变化的,我们可以这样

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

通过上面我们就可以清晰的看出匿名函数的i在for循环的过程中是变量,匿名函数被调用时会去作用域链中查找这个变量,for循环未结束之前循环一次i会加一。

但是这往往不是我们想要的结果,我们想要把这个i保存下来,不随着for循环的改变而改变,让这三个匿名函数作用域链中的i不再指向for循环中的i,我们可以创建一个块级作用域,将i保存下来,我么就可以用上面介绍的立即执行函数来改造一下

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
       (function(i){
           arr.push(function () {
               console.log(i) ;
           });
       })(i)


    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1();
f2();
f3();

这样每次for循环的过程中,都会产生一个新的立即执行函数,而每个立即执行函数产生一个新的作用域,来保存不同的i,当执行arr中的匿名函数的时候就会找到已经保存下来的i(因为函数的传值的按值传递的,所以可以将i保存下来)。

上面的方法是传统的方法,现在解决作用域我们还有一个新型武器 let ,let声明的变量会自动产生作用域(let的其他特性在这不做介绍),现在我们可以用let来达到相同的效果,为arr中的每一个匿名函数创建一个新的作用域

function count() {
    var arr = [];
    for (let i=1; i<=3; i++) {
        arr.push(function () {
             console.log(i) ;
        });

    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1();
f2();
f3();
 console.log(123) 
posted @ 2019-10-06 12:02  Q_Qing  阅读(179)  评论(0编辑  收藏  举报