概念

闭包(closure)是一个拥有任意变量以及绑定这些变量的环境(environment)的表达式(一般来说是就是function)

A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).

在作用域内且在function定义时被访问的变量,那么这个变量就一直能够被那个function访问。

variables that are in scope and accessed from a function declaration will stay accessible by that function.

下面这个例子就是闭包,displayName函数能够访问到不在其代码块里的name变量。

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();

函数的作用域 functional scoping

一个变量的作用域是以其所在的源代码的位置来定义的,嵌套在里面的function可以访问到声明在外层作用域的变量

The scope of a variable is defined by its location within the source code, and nested functions have access to variables declared in their outer scope.

还是拿刚才那个例子来说,displayName函数是嵌套在init函数里的,所以它能够访问到init函数里的变量

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();

闭包的组成

先看一下这个例子:

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

按照java或C++的经验,局部变量name的生命周期在函数的执行后就结束了,所以会推断namemakeFunc()访问后应该就访问不到了。
然而事实恰恰相反,唯一的解释就是myFunc是一个闭包(closure)。

闭包由两部分组成:

  1. function

  2. 创建该function的环境(创建闭包时,作用域内的所有局部变量)

对应到上面的这个例子里:

  1. function: displayName

  2. 环境:name="Mozilla"

再看一个例子:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

alert(add5(2));  // 7
alert(add10(2)); // 12

这个例子说明闭包的function可以是相同的,但是环境可以是不同的,因此就会有不同的结果。

归纳

因此可以将闭包归纳为:

  1. 定义时,确定可访问变量

  2. 执行时,确定变量的值

常见错误

下面这段代码实际上执行的时候并不是alert 0,1,2,3,4,而是alert 5次5。
这是为什么?因为i变量在for循环后变成了5,而在执行的时候我们才会确定闭包里i的值,在定义的时候不会记住i的值是什么的。

var funcs = [];
for(var i=0; i < 5; i++) {
  funcs[i] = function() { alert(i); }
}

for(var j=0; j < funcs.length; j++) {
  funcs[j]();
}

正确的写法是:

var funcs = [];
function makeFunc(x) {
  return function() { alert(x); }
}
for(var i=0; i < 5; i++) {
  funcs[i] = makeFunc(i)
}

for(var j=0; j < funcs.length; j++) {
  funcs[j]();
}

闭包实践

函数工厂

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

私有变量

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

alert(Counter.value()); /* Alerts 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* Alerts 2 */
Counter.decrement();
alert(Counter.value()); /* Alerts 1 */

在这个例子里:

  • 外界不能访问: privateCounter,changeBy

  • 外界间接访问: increment,decrement,value

私有变量+函数工厂

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();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */

Counter1和Counter2绑定的环境相互独立。

性能问题

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

执行一次,就会重新构造两个函数。

正确的做法应该是:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

参考资料


chanjarster
4.2k 声望244 粉丝