写在最前
函数和对其周围状态(即词法环境)的引用捆绑在一起构成闭包。所以说,正是因为闭包,内部函数才可以访问外部函数的作用域。
词法作用域
词法作用域就是,声明变量的位置决定了能够使用这个变量的范围。如果理解不了这个词,可以将词法作用域的“法”字看成一个动词,然后再去理解一下前面的这句话应该就能明白了。
闭包
下面是MDN上面的一段代码
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
调用makeFunc返回了makeFunc的一个内部函数displayName,然后执行。结果浏览器成功alert了name的值。
可以看到,displayName这个函数在执行的时候,makeFunc已经执行完了,当makeFunc执行完之后其内存中的作用域应该已经被销毁了,但为什么这个时候执行displayName还是会成功的获取到name的值呢?就是因为闭包!
JavaScript中的函数是会形成闭包的,闭包是由函数和该函数的词法环境组合而成的,函数的词法环境显然包含了环境内所有的局部变量。当displayName声明的时候,就形成了闭包,displayName这个函数实例会维持一份对它的词法环境(其中包含name)的引用,所以当后面执行对displayName实例的引用的时候,依然可以获取到name的值。
说白了就是,js的变量声明的位置决定了它的使用范围,在这个范围内就确定了谁可以用到这个变量谁会用到这个变量,不管什么时候,需要用到这个变量的方法永远都能获取到这个变量,就算变量作用域被销毁了,它也会维持一份这个变量的内存,以供后续调用。
用闭包模拟私有方法
也是MDN的一个例子,看来让人眼前一亮
JS是没有Java的私有方法的概念的,但很多时候为了避免全局污染,或者是避免非公共方法与公共方法混为一团,我们可以用闭包来模拟私有方法,使用一个闭包来定义一个公共方法,使其拥有和访问自己的私有方法和私有变量,及模块模式。
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.increment,Counter.decrement 和 Counter.value。
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。