周末知识-你知道为什么需要闭包吗?

        我们都知道闭包有一些很好的特性,但是你知道为什么需要闭包吗?闭包的出现究竟是为了解决什么问题?今天我们就从作用域、生存期两个基础的概念聊一聊。

作用域(Scope)

        作用域主要是指程序中变量、函数、类等起作用的范围,可以先看下面的例子:


function main() {
    let a = 10; // 外部变量a
    {
          let a = 2; // 内部变量a
          console.log(a); // 输出 2
    }
    console.log(a) // 输出 10
}

根据输出可以看出,在花括号内的变量a覆盖了外部的变量a,出了花括号,就又是外部变量a了,这是典型的块作用域,再来看另外一个例子:


function main() {
    var a = 10; // 外部变量a
    {
        var a = 2; // 内部变量a
        console.log(a); // 输出2
    }
    console.log(a); // 输出2
}

这段代码和第一段代码除了let和var的区别,其他都是一致的,但是输出却不相同,在这个例子中内部的a和外部的a其实是同一个变量,它等价于下面的代码:

function main() {
    var a = 10; // 外部变量a
    {
        a = 2; // 内部变量a
        console.log(a); // 输出2
    }
    console.log(a); // 输出2
}

变量a的作用范围是整个函数,这是典型的函数作用域。下面看一下与作用域紧密关联的另一个概念。 

 生存期(Extent)

       生存期是变量可以访问的时间段,也就是从分配内存给它,到收回它的内存之间的时间,查看下面代码:


function main() {
    {
      let a = 2;
      console.log(a); // 输出2
    }
    console.log(a); // ReferenceError: a is not defined
}

发现块作用域的变量出了块作用域以后就被回收了,也就是说他的生存期是从申明开始到整个块结束,下面看看函数作用域的情况:


function main() {
    {
        var a = 2; // 内部变量a
        console.log(a); // 输出2
    }
    console.log(a); // 输出2
}

console.log(a); // ReferenceError: a is not defined

a出了块以后还是有效的,直到退出函数,才被回收,也就是说函数作用域的的变量的生存期整个函数内部。 

总结:块作用域的变量生存期是块,函数作用域的变量生存期是函数。

到目前为止所有的例子中,变量出了对应的作用域就被销毁了,现在来看另一个例子:


function outer() {
      var a = 1;
      function inner() {
          a = a + 1;
          return a;
      } 
      return inner;
}

const inner = outer();

console.log(inner()); // 2
console.log(inner()); // 3

 

这个例子和上面分析是不一致的,在函数outer退出后,变量a一直可以被inner引用到。从outer的角度来说,变量a出了outer以后应该被销毁,但是从inner的角度来说,在定义函数的时候可以引用到a,那么就应该一直可以使用变量a,一个说要销毁,另一个却想一直引用,这就产生了矛盾,为了解决这个矛盾,需要引入闭包(Closure)的概念: 闭包是由函数以及创建该函数的环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量。对函数inner来说,它包含一个环境,在这个环境中打包了一个变量a,每次inner使用到变量a的时候其实都是从这个环境中获取的。

        其实回想一下就会发现,只要是函数能作为值传来传去(函数作为一等公民),就会产生这样的矛盾(这是在设计语言的时候就决定了的),闭包是为了能让函数在这种情况下还能继续运行所提供的一种解决方案。在这种解决方案中,可以获得一种不错的特性——隐藏函数所使用的数据,这反而成了闭包的优点。比如可以像下面这样封装Person:


function person(name) {
     var _name = name, _age = 0; // 私有变量

     function print(message) { // 私有方法
         console.log(message);
     }

     function speak() { //公有方法
        print('我的名字叫' + _name + ',我今年' + _age + '岁');
     }

     function setAge(age) { //公有方法
       _age = age;
     }

     // 返回操作数据的所有对外接口
     return {
       speak, //公有方法
       setAge //公有方法
    }
}

var p = person('思·行');
p.setAge(12);
p.speak(); // 我的名字叫思·行,我今年12岁

这样通过闭包就可以模拟类,并且提供了私有方法、公有方法、私有成员变量等这些面向对象的特性。

        通过基础的作用域和生存期的分析,发现了一些函数在特殊场景下使用时会碰到的异常情况,为了解决这些问题,引出了闭包,现在相信对闭包的理解应该更加深刻了。

可以关注我的公众号获取第一时间更新内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值