浅谈闭包

Author Avatar
carvenzhang 3月 11, 2016
  • 在其它设备中阅读本文章

闭包 – closure, 应该可以说是javascript的一个难点吧, 其实说难也不难, 只是因为没有真正一个权威的人/书去给他一个真正的定义。
不过,学编程的人一路都要有自己的理解,很少人乐意去v死记一个定义。
自己看闭包也有很久了,但是每次回想起来又忘了自己改如何形容它, 它真的很难吗?其实并没有,每次看别人的博客,还是很快就能理清楚思路的,
于是终究还是要自己写下来,自己给自己一个理解。

苍白描述

闭包可以理解为,在函数(命名为A函数)内部创建一个内部函数,并暴露到A函数外部去(暴露方法可以是返回一个内部函数,或者将内部函数赋值给 全局/外部 变量),
然后可以通过暴露出来的内部函数,操作A函数内部的变量。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
–阮一峰(学习Javascript闭包(Closure)

作用域(Scope)

闭包的特色是依赖于作用域实现的。
关于作用域,这里有一篇很好的公众号文章
理解JavaScript中的作用域和上下文

简单来说,每个变量被定义时,都绑定在了一个作用域中,作用域有全局的和局部的。
比如

var a = 1;
var fun1 = function () {
    var b = 1;
    var fun2 = function () {
        //...
    }

    console.log(a);//1
    console.log(fun1);//[Function]
    console.log(b);// 1
    console.log(fun2);// [Function]
}

console.log(a);//1
console.log(fun1);//[Function]
fun1();
console.log(b);// 报错 not found
console.log(fun2);// 报错 not found

上面的例子中, a、fun1 都是全局变量,在全局中声明; b、fun2 都是 fun1 内部的 局部变量, 在fun1中声明。
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。
通过作用链域,可以决定变量的访问。
作用链域的寻找可以理解成可以向上爬寻的。
上面代码的作用链域可以展示如下

    window(全局作用域,包含 a、fun1)
      ^
      |
      |
      |
    fun1(fun1 函数内部作用域,包含b、fun2)

当 fun1的语句在内部找不到需要的变量是,就会沿着箭头向上寻找外部的作用域,如果找不到再向上,直到找到或者到了全局作用域。
但是, 箭头上面的语句却不可以向下寻找作用域,所以外部语句不能访问到内部变量(这是一般情况下)

闭包

那么怎么才能 是函数外部能够访问内部的变量呢,闭包可以做到。
引用学习Javascript闭包(Closure)的代码

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。
它一共运行了两次,第一次的值是999,第二次的值是1000。
这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1。
因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

闭包的作用

闭包的特性就在外部读取函数内部的变量。
这个特性有什么作用呢,思路是因人而异的。
比如

模拟对象的私有属性,只能通过函数内部的方法访问函数的属性
面向对象编程

可以一一衍生下去