聊聊JS中的闭包

786 阅读2分钟

聊闭包,逃不过要说变量作用域

变量作用域

概念

一个变量可以使用的范围

看几个例子加深印象
  • 例子1
  var age = 20;
  function fn(callback){
        var age=18;
        callback()
    }
  fn(function(){
        console.log(this);//this指向window
        console.log(age);//20
   }

从上面例子看出,查找age变量的步骤:查找当前作用域;再查找上一级作用域(全局作用域),找到了

对于这个例子,为什么上一级作用域是全局作用域?而不是fn函数?因为,看上一级作用域,不是看函数在哪调用,而是函数写在哪。

  • 例子2
function fn(){
        var a=5;
        return function(){
            a++;
            console.log(a);
        }
    }
    var f1=fn();  //f1指向匿名函数
    f1();       //6
    f1();       //7
    f1();       //8

看到上面代码的打印,为什么没有释放变量?一般函数执行完毕,变量会释放,但是js引擎发现匿名函数需要使用a变量,所以把a变量放到匿名函数可以访问到的地方了。显然,这是个闭包

如果要释放a变量,要用 f1=null

作用域链

为什么要有作用域链

查找变量,上面第一个例子查找a变量,就是通过作用域链查找的。

查找变量步骤

  • 查看当前作用域,若无,找上一级作用域。一直找下去,直到全局作用域。

闭包

闭包的概念

函数A内部有一个函数B,函数B可以访问到A中的变量,那么函数B就是一个闭包

闭包的意义

模块化、保护变量(我们只能间接访问函数内部的变量)

实际运用1

去一些餐厅,可能会有“最低消费”这一说法。我们用闭包模拟餐厅系统。

用自调用函数实现模块化,闭包保护变量leastPrice(需要查看外部访问者的权限),

  var dining = (
        function() {
            var total = 0
            var leastPrice = 30
            return {
            //计算买的总价
                buy: function(price) {
                    total += price
                },
                //付钱
                pay: function() {
                    if (total < leastPrice) {
                        console.log('请继续点餐');
                        return
                    }
                    console.log('点餐成功');
                },
                edit: function(id, val) {
                //判断是否有权限
                    if (id == '888') {
                        leastPrice = val
                    }else{
                     console.log('权限不足')
                    }
                }
            }
        }
    )()

调式过程:

dining.buy(10)//买10块
dining.pay()//打印 请继续点餐
dining.buy(20)//再买20
dining.pay()//付钱  打印 点餐成功
dining.edit(888,10)//修改最低消费
dining.buy(12)
dining.pay()//打印 点餐成功
实际运用2

经典题,解决var变量定义函数的问题

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

setTime异步函数,循环结束后,i=6,所以最后会每隔一秒打印6,并不是我们想要的 1 2 3 4 5

闭包解决:

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

这样,打印的就是 1 2 3 4 5