JavaScript 闭包原理与核心应用场景解析
本文深入解析 JavaScript 闭包的本质,即函数内部子函数读取外部局部变量的机制。详细阐述闭包在 setTimeout 传参、事件回调绑定、函数防抖实现以及私有变量封装等场景中的具体应用,通过代码示例说明如何利用闭包维护状态并避免全局污染。
闭包的基本原理
一、闭包
在 JavaScript 语言体系中,只有函数内部的子函数才能访问其局部变量。所谓闭包,就是指那些能够读取其他函数内部变量的函数。从本质上看,闭包充当了连接函数内部与外部环境的桥梁。
以下代码示例展示了这一机制:
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result();//999
函数 f2 被包含在函数 f1 内部,此时 f1 的所有局部变量对 f2 均可见。反之,f2 内部的局部变量对 f1 则不可见。
这体现了 JavaScript 特有的“链式作用域”(chain scope)结构。子对象会逐层向上查找父对象的变量,因此父对象的所有变量对子对象可见,但反向不成立。
既然 f2 可以访问 f1 的局部变量,只需将 f2 作为返回值,即可在 f1 外部读取其内部变量。
闭包的核心应用场景
二、闭包的使用场景
setTimeout 传参
1. setTimeout
原生 setTimeout 传递的第一个函数参数默认不支持直接传参,利用闭包可以实现传参效果。
function f1(a) {
function f2() {
console.log(a);
}
return f2;
}
var fun = f1(1);
setTimeout(fun,1000);//一秒之后打印出1
事件回调绑定
2. 回调
定义行为,然后将其关联到某个用户事件(如点击或按键)。代码通常作为回调函数(事件触发时调用的函数)绑定到事件上。
以下代码展示了这一过程:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" id="size-12">12</a>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" id="size-20">20</a>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" id="size-30">30</a>
<script type="text/javascript">
function changeSize(size){
return function(){
document.body.style.fontSize = size + 'px';
};
}
var size12 = changeSize(12);
var size14 = changeSize(20);
var size16 = changeSize(30);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-20').onclick = size14;
document.getElementById('size-30').onclick = size16;
</script>
</body>
</html>
当点击数字链接时,字体大小会变为对应的数值。
函数防抖实现
3. 函数防抖
在事件触发 n 秒后再执行回调,若在这 n 秒内再次触发,则重新计时。
实现的关键在于 setTimeout 函数。由于需要保存计时器变量,为保持全局环境纯净,可借助闭包来实现状态维护。
具体代码实现如下:
/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
let timer = null //借助闭包
return function() {
if(timer){
clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
timer = setTimeOut(fn,delay)
}else{
timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
}
}
}
私有变量封装
4. 封装私有变量
以下代码演示了如何使用 JavaScript 创建计数器:
方法1:
function f1() {
var sum = 0;
var obj = {
inc:function () {
sum++;
return sum;
}
};
return obj;
}
let result = f1();
console.log(result.inc());//1
console.log(result.inc());//2
console.log(result.inc());//3
在返回的对象中实现了一个闭包,该闭包携带了局部变量 x。从外部代码无法直接访问到变量 x。
方法2:
function f1() {
var sum = 0;
function f2() {
sum++;
return f2;
}
f2.valueOf = function () {
return sum;
};
f2.toString = function () {
return sum+'';
};
return f2;
}
//执行函数f1,返回的是函数f2
console.log(+f1());//0
console.log(+f1()())//1
console.log(+f1()()())//2
除 null 外,所有 JavaScript 数据类型均拥有 valueOf 和 toString 这两个方法。valueOf() 返回指定对象的原始值,toString() 返回对象的字符串表示。在数值运算中优先调用 valueOf,在字符串运算中优先调用 toString。表达式 sum+' ' 属于字符串类型数据。
以上为 JavaScript 闭包使用场景的详细内容,更多关于 JavaScript 闭包的资料请关注本站其它相关文章!
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
