闭包
2019 javascript闭包( closure ), 也叫词法闭包( lexical closure )或函数闭包( function closure )。语义上,闭包是一个保存了函数及其环境的记录。
在 MDN 上定义 JavaScript 中的闭包:A closure is the combination of a function and the lexical environment within which that function was declared。(闭包是一个函数以及声明该函数的词法环境的组合)。
理解词法作用域( lexical scoping )的规则–函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是在函数调用是决定的。所以,为了实现这种词法作用域,JavaScript 函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。
个人理解一个闭包必备的条件:
- 一个函数
- 该函数提供了外部访问函数内部变量的返回
词法作用域与闭包
词法作用域跟闭包有着很相似的结构,但是有着本质的区别。正常情况下,init
函数执行之后,通常函数创建的内部作用域被销毁。而闭包 makeFunc
可以阻止函数创建的作用域销毁。
词法作用域:
function init() {
var name = 'Mozilla';
function displayName() {
console.log(name);
}
displayName();
}
init();
闭包:
function makeFunc() {
var name = 'Mozilla';
function displayName() {
console.log(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
在循环中建立闭包
以下是一个很常见的例子
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
我们的预期是分别输出 1 ~ 5,每秒输出一个数字。但上述代码将 5 个匿名函数分别间隔 1~5 秒放入异步队列中,当异步队列开始执行的时候,此时 i 作为全局作用域中的变量,其值是 6。记住一点,嵌套的函数不会将作用域内的私有成员复制一份,也不会对绑定的变量生成静态快照(static snapshot)。
第一个解决方式:创建一个闭包,或者使用立即执行表达式创建匿名闭包函数。
// 使用闭包
function makeTimeout(i) {
return function () {
setTimeout(function () {
console.log(i);
}, i * 1000);
};
}
for (var i = 1; i <= 5; i++) {
makeTimeout(i)();
}
// 使用匿名闭包
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, i * 1000);
})(i);
}
PS: 使用立即表达式会立即执行,如果想延迟执行,请使用闭包。
第二个解决方式:使用 ES6 中的 let
关键字
for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
用途
- 读取函数内部的变量,使用闭包模拟私有方法
- 变量的值始终保持在内存中,不会在函数调用后被自动清除
闭包创建单例模式
可以使用闭包实现单例模式。让一个类仅有一个实例,并且全局可以访问该实例。从而避免一个全局类的频繁创建域销毁。我们在运行创建的时候首先判断该单例是否存在,如果存在则返回,不存在则创建。
var Singleton = function (name) {
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function () {
return this.name;
};
Singleton.getInstance = function (name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
};
// have a try
var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');
console.log(a === b); // true
注意点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。解决方法是,在退出函数之前,将不使用的局部变量全部删除
参考链接
- 《JavaScript 权威指南(第六版)》
- 《JavaScript 高级程序设计(第三版)》
- MDN: Closures
- 维基百科: Closure
- 学习 Javascript 闭包(Closure)