闭包
闭包是个 js 中比较重要的一个概念了,面试前端或者 Node.js 方面的工作几乎95%以上的面试官都会问这方面的问题。但是对初学者来说,闭包是个特别抽象的概念,特别是 ECMAScript 给出的定义:
闭包是函数和声明该函数的词法环境的组合
如果你只是个小白没有实战经验,这句话你懂几个字?
先来看个函数
function init() {
var name = "nys";
// name 是一个被 init 创建的局部变量
function displayName() {
// displayName() 是内部函数,一个闭包
alert(name);
// 使用了父函数中声明的变量
}
displayName();
}
init();
init()
创建了一个局部变量 name
和一个名为 displayName()
的函数。displayName()
是定义在 init()
里的内部函数,仅在该函数体内可用。displayName()
内没有自己的局部变量,然而它可以访问到外部函数的变量,所以 displayName()
可以使用父函数 init()
中声明的变量 name 。但是,如果有同名变量 name 在 displayName()
中被定义,则会使用 displayName()
中定义的 name 。
再看个函数
function makeFunc() {
var name = "nys";
return function displayName() {
alert(name);
}
}
var myFunc = makeFunc();
myFunc();
从运行的结果来看,没有什么不一样,都是会输出 name
的值。但是内部的区别就在于,displayName()
函数在执行前先被返回了,先被返回到了定义的 myFunc
后,再用 myFunc()
执行。在很多的编程语言中,函数内部的变量只能在函数执行期间被使用,一旦 makeFunc()
执行完毕,我们会认为 name
变量将不能被访问。然而,因为代码运行得没问题,所以很显然在 JavaScript
中并不是这样的。
其实,这就是 闭包 ,由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。在我们的例子中,myFunc()
是执行 makeFunc()
时创建的 displayName()
函数实例的引用,而 displayName()
实例仍可访问其词法作用域中的变量,即可以访问到 name
。由此,当 myFunc()
被调用时,name
仍可被访问,其值 nys
就被传递到alert
中。
闭包的用处
在很多的编程语言中,都支持将方法、字段等设置为私有,即把它们当作一个类的内部内容所调用,其调用权限仅限于本类。而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。(也被成为模块模式)
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
在匿名函数 Counter()
内只有一个共享的变量 privateCounter
,但是它被内部的三个函数所共享Counter.increment
,Counter.decrement
和 Counter.value
。三个函数的操作都同时影响唯一变量 privateCounter
。