学习闭包

97 阅读2分钟

闭包的定义

参考MDN关于闭包的定义:函数和其周围状态(lexical environment, 词法环境)的引用捆绑在一起构成闭包(closure)。

词法作用域

作用域是一个和语言无关的概念,总的来说就是代码中所用变量都有一个可用的范围,这个范围就可以叫做作用域。词法作用域,是指在词法分析阶段就确定了。

function outer() {
    var a = '1';
    function inner() {
        console.log(a);
    }
    inner();
}
outer();

outer()函数里面有个局部变量a和一个inner的函数,inner是在内部创建的函数,虽然没有自己的变量a,但是可以访问到outer函数的变量a,因此可以在控制台中打印出1。

闭包

首先看下面的例子:

function outer() {
    var a = '1';
    function inner() {
        console.log(a);
    }
    return inner;
}
outer();

与上一个例子不同的是,将内部函数inner当作返回值返回(在js中,函数作为一等公民,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回)。 一般来说对于一个函数,进入主线程开始执行,主线程会为该函数在内存中分配空间,一旦函数执行完成以后,函数出栈,其变量也会被回收机制销毁,但是对于闭包,outer函数执行完毕以后,作用域并没有随之销毁,因为inner维持着一个对它的词法作用域的引用。

闭包的用处

用闭包模拟私有方法

var makeCounter = function() {
   var privateCounter = 0;
   function changeBy(val) {
      privateCounter += val;
   }
   return {
       increment: function() {
           changeBy(1);
       },
       decrement: function() {
           changeBy(-1);
       },
       value: function() {
           return privateCounter;
       }
   }
};

var counter1 = makeCounter();
var counter2 = makeCounter();
console.log(counter1.value()); /* logs 0 */
counter1.increment();
counter1.increment();
console.log(counter1.value()); /* logs 2 */
counter1.decrement();
console.log(counter1.value()); /* logs 1 */
console.log(counter2.value()); /* logs 0 */

首先makeCounter提供了一个词法环境,其中有变量privateCounter和方法changeBy,为三个函数increment, decrement和value共享。 我们建立了两个独立的词法作用域couter1和couter2,每个闭包都是引用自己词法作用域中的变量,不会相互影响。

性能考量

如果不是特定任务需要使用闭包,在函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本有负面影响。比如在创建新的对象或者类时,方法通常关联于对象的原型,而不是对象的构造函数。

参考