JavaScript 闭包and变量作用域

变量作用域

在讲闭包之前我们先来简单介绍一下JavaScript中的变量作用域。

在ES6之前变量(用var关键字声明的变量)根据作用域的不同分为两种:全局变量和局部变量

在ES6中还新增了块级作用于变量(用let声明的变量),用let声明的变量也分为全局变量和局部变量,只不过是用let声明的局部变量是块级作用域的,作用域的范围比更加精细了。比如我们在一对大括号{}里用let声明的变量则作用域范围仅限于当前的大括号内部。

全局变量:一般情况下只要是在函数外声明变量都是全局变量

局部变量:在函数内部声明的变量为局部变量,只能在函数内部使用,如果使用let声明的变量则作用域范围更小,比如在函数内部的if语句块中声明,则作用域范围仅限if语句块。

用var声明的变量:

  • 函数内部可以使用全局变量
  • 函数外部不可以使用局部变量
  • 语句块外部不能使用语句块内用let声明的变量
  • 当函数或语句块执行完毕,本作用域内的局部变量会销毁

闭包

闭包(closure)是指有权访问另一个函数作用域中变量的函数。

通过前面讲的变量作用域我们知道,一般情况下,在一个函数的内部是没办法访问另一个函数内部定义的变量的,不管是var定义还是let定义,都是无法访问的。

function fun1(){
    var name = 'alvin';
}

function fun2(){
    console.log(name);
}

fun2();//报错ReferenceError:name is not defined

如上述代码所示,在fun2中访问fun1中的变量name,当我们调用fun2时会抛出错误提示:变量未定义。那么如果我铁了心就要在fun2中使用fun1中的name变量呢,就没有别的办法了吗?答案肯定是有的,那就是接下来我们所要介绍的闭包了。我们把上面的代码来稍微改造一下:

function fun1(){
    var name = 'alvin';
    function fun2(){
        console.log(name);
    }
    fun2();
}

fun1();//alvin

上面这段代码就形成了一个闭包。我们来解析一下:上面说过闭包是指有权访问另一个函数作用域中变量的函数。在上面的代码中,fun2(函数)作用域中访问了fun1(函数)作用域(另一个作用域)中的变量name。刚好符合了闭包的定义,所以这里就产生了闭包。

上面这段代码可能会有人产生疑问:fun2本来就是fun1的内部,所以能访问fun1中的变量也是理所应当的。那如果我们想在fun1的外面访问fun1里面的变量能不能实现呢?下面我们来继续改造一下上面的代码:

function fun1(){
    var name = 'alvin';
    function fun2(){
        console.log(name);
    }
    return fun2;//这里不直接调用而是返回
}

var fn = fun1();
fn();//alvin

上面代码中我们在fun1中不直接调用里面的函数fun2,而是将fun2作为返回值返回。然后在外面调用fun1并用变量fn来接收fun1的返回值。我们知道fun1的返回值也是一个函数(fun2),所以变量fn也就是一个函数了,这就相当于我们在全局作用域(fun1的外部)中定义了一个函数fn,而当我们去调用fn函数时结果打印输出了fun1中的变量值“alvin”。这样就实现了在函数fun1的外部访问函数内部的变量了。

闭包的作用

通过上面的代码,我们实现了在函数外部访问函数内部的局部变量。由此可知闭包的主要作用:延伸了变量的作用范围。

前面我们提到函数内部的变量在函数外部是无法访问的,而且在函数执行完毕后变量就会自动销毁。而通过闭包即使函数已经调用执行完毕其内部的变量也不会销毁,同时在函数外部也可以访问到函数内部的变量。所以这也再次印证了闭包的作用是:延伸了变量的作用范围。

闭包的案例

接下来我们用一个例子来演示一下闭包的使用

场景:在页面上有四个div,每个div内部都有一段文字描述,现在想要在页面加载完成5秒后,打印输出所有div中的内容。

分析:因为是打印所有的div内容,所以首先需要获取到所有的div元素,然后再用for循环逐个打印。

           因为是在5秒后才打印,所以这里需要在for循环中使用setTimeout函数进行倒计时。

问题:那么问题就来了,我们都知道for是同步一次性执行完成的,而setTimeout是异步函数,也就是说如果在for循环中使用setTimeout函数,并且setTimeout函数内部需要用到for循环中的计步器,那么就会引发一个问题,即当setTimeout函数真正开始执行的时候,它内部拿到的计步器将永远都会是for循环结束后的最后一个。看下面代码:

<html>
    <head>
        <title>闭包DEMO</title>
    </head>
    <body>
        <div>javascript</div>
        <div>VUE</div>
        <div>HTML</div>
        <div>CSS</div>
        <div>python</div>
        <script>
            function printDivContent(){
                var divs = document.querySelectorAll('div');
                for(var i = 0; i < divs.length; i++){
                    setTimeout(function(){
                        console.log(divs[i].innerHTML);
                    }, 5000);
                }
            }
            
            printDivContent();//5秒后会抛出错误:Uncaught TypeError:Cannot read property 'innerHTML' of undefined

        </script>
    </body>
</html>

我们来看上面代码,5秒后没有得到我们所期望的结果,而是抛出了错误:Uncaught TypeError:Cannot read property 'innerHTML' of undefined。这是因为setTimeout内部的函数开始执行的时候,for循环已经结束所以我们得到的i的值已经变成了5,而divs的最大索引是4,所以实际上divs[5]是一个undefined而undefined并没有任何属性,所以就报出了上述错误。

下面我们将代码改为闭包的形式,再来看一下:

<html>
    <head>
        <title>闭包DEMO</title>
    </head>
    <body>
        <div>javascript</div>
        <div>VUE</div>
        <div>HTML</div>
        <div>CSS</div>
        <div>python</div>
        <script>
            function printDivContent(){
                var divs = document.querySelectorAll('div');
                for(var i = 0; i < divs.length; i++){
                    //定义一个自调用函数
                        (function(index){
                            setTimeout(function(){
                                console.log(divs[index].innerHTML);
                            }, 5000);
                        })(i);//将i作为参数传递给自调用函数                    
                }
            }
            
            printDivContent();
            //5秒后打印输出:javascript VUE HTML CSS python

        </script>
    </body>
</html>

我们看到代码改造后已经输出了我们期望的结果。这里我们定义了一个自调用函数并将for循环的计步器作为参数传递给自调用函数,这样在自调用函数内部就能拿到for循环每次循环的索引值而不是只有最后一个值了。

而在自调用函数内部还有一个匿名函数使用了自调用函数的参数index,这样就形成了一个闭包。我们说过闭包的作用就是延伸了变量的作用范围,从而也就实现了我们期望的效果。

闭包缺点

普通函数中,当函数执行完毕其内部的变量会随之销毁,而闭包中,即使函数已经执行,它内部的变量也不会销毁,而是要等到使用到这个变量的函数执行完成后才会销毁,这样就会造成一定的性能损失

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值