还记得刚开始学习JavaScript时,对闭包的抽象描述真的可谓是一看就会,一写就废。主要是不知道实际使用场景是怎样的,经过这些年被各种文章还有代码洗礼之后,发现其实也不过如此。但是在实际开发过程中,一定要考虑使用闭包的场景其实不多,相反地,如果滥用闭包还可能造成更多问题。本着丑化说在前头的原则,我还是先说说该注意的地方,再来看它的用武之地。
性能考虑
内存使用
function heavyClosure() {
let largeArray = new Array(1000000).fill("closure");
return function() {
console.log("Closure referencing a large array");
}
}
let closure = heavyClosure();
闭包会保持对其创建时作用域的引用,导致可能的内存泄漏。比如上面这个例子,即使heavyClosure
函数执行完毕,largeArray
也不会被垃圾回收,因为closure
持有对它的引用。这应该算是最常见的一种场景,我在这里构造了一个长度为100000的大数组。
代码可读性和维护性
复杂的闭包使用
function setupElements() {
let elements = document.querySelectorAll('.some-elements');
let size = 10; // 初始大小
for (let i = 0; i < elements.length; i++) {
elements[i].onclick = (function(size) {
return function() {
elements[i].style.fontSize = size + 'px';
size++;
}
})(size);
}
}
上面的代码为每一个元素绑定一个点击事件,但实际上使用了立即执行函数创建了闭包,目的却只是为了保存size变量,显然有点大材小用了,而且很讨厌阅读这样的代码,这里的闭包其实完全是没必要的,我们完全可以这么写。
使用箭头函数及forEach,这样看起来顺眼多了,至少别人读起来也不会有什么心智负担
function setupElements() {
const elements = document.querySelectorAll('.some-elements');
let size = 10; // 初始大小
elements.forEach((element) => {
element.onclick = () => {
element.style.fontSize = `${size}px`;
size++;
};
});
}
闭包的作用
丑话说完,当然也该说说它的用武之地了。
1. 数据封装和管理
闭包可以用来封装数据,提供一个方法来访问和修改私有变量,同时保护这些变量不被外部直接访问。下面的计数器示例就确保了count变量只能通过其内部的方法进行操作
创建一个计数器
function makeCounter() {
let count = 0;
return {
increment: function() {
count += 1;
},
decrement: function() {
count -= 1;
},
getValue: function() {
return count;
}
};
}
const counter = makeCounter();
counter.increment();
console.log(counter.getValue()); // 输出: 1
counter.decrement();
console.log(counter.getValue()); // 输出: 0
2. 创建模块化代码
利用闭包,可以创建模块化的代码,使得每个模块都有自己的私有作用域,有助于避免全局命名空间污染和名称冲突。
模块模式
var myModule = (function() {
let privateVar = "I am private";
return {
publicMethod: function() {
console.log(privateVar);
}
};
})();
myModule.publicMethod(); // 输出: I am private
这里,myModule
是一个封装良好的模块,其中privateVar
对外部是隐藏的,只能通过publicMethod
方法间接访问。
3. 函数柯里化和部分应用
刚开始总是记不住函数柯里化是什么,还自己给自己挖了坑,面试官追问什么是函数柯里化,我提了一嘴但是始终组织不起来语言,说得一塌糊涂。后来发现其实结合闭包来理解,这一切就显得非常自然了,说通俗一些,函数柯里化就是将一个接收多个参数的函数,“改造”为一个对外来说只接收一个参数,由其内部实现的方法链式保存参数的函数。
函数柯里化
function multiply(a) {
return function(b) {
return a * b;
};
}
const double = multiply(2);
console.log(double(3)); // 输出: 6
const triple = multiply(3);
console.log(triple(3)); // 输出: 9
上面的代码,multiply
函数通过闭包返回一个新函数,这个新函数记住了multiply
的第一个参数a
,使得我们可以创建double
、triple
等特定功能的函数。
结论
到此为止,由于业务功能开发的时候,并不需要保证其优秀的模块化和封装能力,所以对于闭包的使用可能还是会偏向保守一些,以后还是要结合优缺点及自身能力来决定如何使用闭包。