本文深入解析 JavaScript 中的闭包概念,通过函数作为返回值和参数两种形式阐述其核心原理。结合点击列表索引的经典案例,分析变量释放与内存泄露问题,并提供小米前端闭包面试题的完整解答,帮助开发者掌握闭包在实际开发中的应用。
闭包的基本概念
在深入理解闭包之前,许多开发者往往对这一概念感到困惑。只有当掌握了 JavaScript 的作用域机制以及 this 指向的相关规则后,才能更清晰地理解闭包的本质。
闭包的核心原理
闭包(closure)是前端面试中的高频考点。从本质上讲,它表现为函数嵌套函数的结构。
function foo () {
var a = 1;
return function () {
a++;
console.log(a);
}
}
var aaa = foo();
aaa(); //2
aaa(); //3
当函数作为返回值被调用时,内部函数引用了外部函数的局部变量。例如,在代码中,变量 aaa 指向了 foo() 返回的一个新函数。由于该内部函数引用了变量 a,即使 foo 函数执行完毕,变量 a 也不会被垃圾回收机制释放,而是继续保留在内存中。因此,后续调用 aaa() 时,a 的值会依次递增,分别输出 2 和 3。
另一种常见场景是将函数作为参数传递。
var a = 10;
function foo () {
console.log(a);
}
function aaa(fn) {
var a = 100;
fn();
}
aaa(foo);
按照以往对作用域链的理解,当在 aaa 函数内部执行 fn 函数时,如果 fn 自身没有定义变量 a,它会沿着作用域链向上查找,最终找到父级作用域中的变量 a,其值为 100。那么,结果是否就是 100 呢?
实际运行结果并非如此,而是输出 10。正如王福朋老师在博客中所指出的,闭包在取值时创建的是函数定义时的作用域快照,而不是动态查找的“父作用域”。这意味着闭包捕获的是变量在定义时刻的值,而非执行时刻的作用域链。
闭包的常见应用场景
了解闭包原理后,我们需要探讨其实际使用场景。
虽然闭包概念较为抽象,但可以通过一个简单的例子来理解:当点击列表项(li)时,弹出该列表项在父级无序列表(ul)中的索引位置。
以下是相关的 HTML 结构代码。
<ul>
<li>001</li>
<li>002</li>
<li>003</li>
</ul>
接下来是处理点击事件的 JavaScript 代码。
在示例 1 中,观察代码运行结果会发现,无论点击哪一个 li 元素,弹出的索引值始终是 3。
var aLi = document.getElementsByTagName('li');
for (var i = 0; i<aLi.length; i++) {
aLi[i].onclick = function() {
alert(i);
}
}
这是因为在匿名点击事件函数内部并没有定义变量 i。当 for 循环结束后,变量 i 的值已经变为 3。此时再点击页面中的 li 标签,匿名函数访问的是循环结束后的全局变量 i,因此结果恒为 3。
针对上述问题,示例 2 提供了一种解决方案。
aLi[i].onclick = (function(i){
return function(){
alert(i);
}
})(i);
该方案利用函数作为返回值,并通过立即执行函数(IIFE)将变量 i 作为参数传入。由于返回的内部函数引用了这个局部变量 i,即使 for 循环结束,i 的值也不会被释放,而是保存在内存中。这种机制虽然解决了索引错误问题,但在低版本 IE 浏览器中,由于闭包导致变量无法及时释放,可能会引发内存泄露问题。
示例 3 展示了另一种解决思路。
for (var i = 0; i<aLi.length; i++) {
(function(i){
aLi[i].onclick = function(){
alert(i);
}
})(i);
}
其核心原理与示例 2 类似,都是通过闭包机制将变量 i 的值固化在内部函数的作用域中,从而确保每次点击时获取正确的索引值。
闭包面试题解析
最后,来看一道来自小米前端的闭包面试题。
function repeat (func, times, wait) {
} //这个函数能返回一个新函数,比如这样用
var repeatedFun = repeat(alert, 10, 5000)
//调用这个 repeatedFun ("hellworld")
//会alert十次 helloworld, 每次间隔5秒
以下是针对该面试题的完整解答代码。
function repeat (func, times, wait) {
return function(str) {
while (times >0) {
setTimeout(function(){
func(str);
},wait);
times--;
}
}
}
var repeatedFun = repeat(alert, 10, 100);
repeatedFun ("hellworld");
以上便是关于 JavaScript 闭包的详细解析,希望这些内容能帮助你更好地掌握闭包知识,并在实际开发中灵活运用。