Open ArthurWangCN opened 2 years ago
闭包是指有权访问另一个函数作用域中变量的函数,优点是私有化数据,在私有化数据的基础上保持数据,缺点使用不恰当会导致内存泄漏,在不需要用到的时候及时把变量置为null。
闭包的应用是非常广泛的,比方我们常见的节流,防抖,函数柯理化。
MDN闭包: 一个函数和对其周围状态(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
看个示例:
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
在一些编程语言中,一个函数中的局部变量仅存在于此函数的执行期间。一旦 makeFunc() 执行完毕,你可能会认为 name 变量将不能再被访问。然而,因为代码仍按预期运行,所以在 JavaScript 中情况显然与此不同。
原因在于,JavaScript 中的函数会形成了闭包。 闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。因此,当 myFunc 被调用时,变量 name 仍然可用。
闭包的用途:
1. 实现面向对象编程
闭包允许将函数与其所操作的某些数据(环境)关联起来,这显然类似于面向对象编程。
传统的对象语言都提供类的模板机制,这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,我们可以模拟出这样的机制:
function Person(){
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
var john = Person();
john.setName("john");
console.log(john.getName());
var jack = Person();
jack.setName("jack");
console.log(jack.getName());
2. 用闭包模拟私有方法
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。
而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为模块模式(module pattern)。
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();
console.log(counter1.privateCounter); // undefined
console.log(counter1.value()); // 0
counter1.increment();
console.log(counter1.value()); // 1
以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。
3. 缓存耗时的操作结果
设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间。
那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。
闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
var CachedSearchBox = (function () {
var cache = {},
count = [];
return {
attachSearchBox: function (id) {
if (id in cache) { // 如果结果在缓存中
return cache[id]; // 直接返回缓存中的对象
}
var result = new uikit.webctrl.SearchBox(id); // 新建
cache[id] = result; // 更新缓存
if (count.length > 100) { // 保证缓存的大小<=100
delete cache[count.shift()];
}
return result;
},
};
})();
CachedSearchBox.attachSearchBox("input1");
使用闭包的注意点:
解决了什么问题:
缺点:
闭包使用不当可能造成内存泄漏
不复杂,本质就是上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。