关于闭包的部分理解

什么是闭包

如果一个函数,用了它范围外的变量,那么(这个函数 + 这个变量)就叫做闭包,闭包有权访问其包含函数内部的所有变量,闭包的作用域链包含自己的作用域、包含函数的作用域和全局作用域。当函数返回一个闭包时,这个函数的作用域将会一直存在于内存中,直到闭包不存在。

闭包的作用

  1. 模仿块级作用域(匿名自执行函数)

    1
    2
    3
    4
    5
    6
    function countNumber(n) {
    for(var i = 0; i < n; i++) {
    console.log('i1',i)
    }
    console.log('i2',i)
    } // i2 5
    1
    2
    3
    4
    5
    6
    7
    8
    function countNumber(n) {
    (function(){
    for(var i = 0; i < n; i++) {
    console.log('i1',i)
    }
    })()
    console.log('i2',i)
    } // i2 i is not defined

    在第二个函数中,在 for 循环外部插入了一个块级作用域。原因是在匿名函数中定义的任何变量,都会在执行结束时被销毁,因此变量 i 只能在循环中使用,使用后即被销毁。这个块级作用域能访问变量 n,是因为这个匿名函数是一个闭包,它能够访问包含作用域中(也就是 countNumber 函数)中的所有变量。这个方法可以尽量避免在全局环境中添加过多变量和函数。也可以减少闭包占用内存的问题,因为没有指向匿名函数的引用。

  2. 访问私有变量
    任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

    1
    2
    3
    4
    function add(n1, n2){
    var sum = n1 + n2
    return sum
    }

    在这个函数内部,有3个私有变量:n1、n2 和 sum。函数内部可以访问这几个变量,但是函数外部则不能访问它们。如果在这个函数内部创建一个闭包,则可以通过闭包访问这些变量。

    • 从外部读取函数内部的变量

      1
      2
      3
      4
      5
      6
      7
      8
      9
      function fn1() {
      var n = 1
      function fn2() {
      return n
      }
      return f2
      }
      var result = f1()
      result() //1
    • 对象的继承

      • 构造函数定义公有方法

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        function MyObject() {
        //私有变量和私有函数
        var privateVariable = 10

        function privateFunction() {
        return false
        }
        //公有方法
        this.variable = function() {
        return privateVariable++
        }
        this.function = function() {
        return privateFunction()
        }


        }

        var obj = new MyObject()
        obj.variable()// 10
        obj.variable()// 11
        obj.function()// false

        利用这个方法,可以隐藏不想被直接修改的数据,比如:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        function Person(name) {
        this.getName = function() {
        return name
        }
        this.setName = function(value) {
        name = value
        }
        }

        var person = new Person('yang')
        person.getName() //yang
        person.setName('zhang')
        person.getName() //zhang

        在上述代码中,定义了两个公有方法:getName() 和 setName()。这两个函数都可以在函数外部使用,而且它们都是在构造函数内部定义的,作为闭包都可以访问私有变量 name。在构造函数外部,没有任何办法访问到name需要注意的是这个方法的私有变量 name 在每个 Person 实例中都会不同,因为每次调用构造函数都会重新创建这两个方法。

        • 原型模式(静态私有变量)
  3. 结果缓存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function count(n) {
    return function () {
    return n++;
    };
    }
    var c1 = count(1);
    c1() // 1
    c1() // 2
    c1() // 3

    闭包的一个特点就是只要存在引用函数内部变量的可能,JavaScript就需要在内存中保留这些变量。而且JavaScript运行时需要跟踪这个内部变量的所有外部引用,直到最后一个引用被解除(主动把外部引用赋为 null 或者页面关闭),JavaScript的垃圾收集器才能释放相应的内存空间,所以闭包不会释放外部的引用,从而函数内部的值可以得以保留。