前端学习14 闭包
1.闭包
闭包是 JavaScript 中的一个核心概念,它允许函数访问其定义时的词法作用域中的变量。这种特性使得闭包在数据封装、回调函数、模块模式等场景中有着广泛的应用。
1.1 闭包的定义:
闭包是指有权访问另一个函数作用域的变量的函数。在JavaScript中,闭包可以通过函数嵌套和变量应用实现。
function a() {
let x = '我在outer函数里!';
function b() {
console.log(x);
}
return b;
}
const c = a();
c(); // 输出: 我在outer函数里!
在这个例子中,b函数引用了x变量,因此JavaScript引擎会保留a函数的作用域链,以便b函数可以访问x变量。
1.2闭包的实现
闭包是指一个函数可以访问它定义时所在的词法作用域以及全局作用域中的变量。当内部函数引用外部函数的变量时,外部函数的作用域链将被保留在内存中,以便内部函数可以访问这些变量。
function a() {
var aa = 333;
function b() {
console.log(aa); // 输出:333
}
return b;
}
var demo = a();
demo(); // 输出:333
在这个例子中,b函数引用了a函数中的变量aa,因此即使a函数执行完毕后,aa变量仍然可以通过b函数被访问,形成了闭包。
2.闭包的用途
2.1 封装私有变量
闭包可以用于封装私有变量,以防止其被外部访问和修改。这可以减少全局变量的数量,降低全局变量被误用或意外修改的风险。
function addFn() {
let count = 1;
function increment() {
count++;
console.log(count);
}
return increment;
}
const counter = addFn();
counter(); // 输出:2
counter(); // 输出:3
在这个例子中,increment函数通过闭包访问并修改了count变量,而count变量不会被外部直接访问。
2.2 做缓存
闭包可以用于缓存计算结果,减少重复计算。
function fn1() {
var type = 'JavaScript';
let tt1 = 1;
const tt2 = 2;
var innerBar = {
getType: function() {
console.log(tt1);
return type;
},
setType: function(newType) {
type = newType;
}
};
return innerBar;
}
var bar = fn1();
console.log(bar.getType()); // 输出:1 JavaScript
bar.setType('Python');
console.log(bar.getType()); // 输出:1 Python
在这个例子中,getType和setType方法通过闭包访问并修改了type变量。
2.3 模块化编程
闭包可以用于实现模块化编程,封装模块的私有变量和方法。
const moduleFn = (function() {
let privateVar = '我是私有变量!';
function privateMethod() {
console.log(privateVar);
}
return {
publicMethod: function() {
privateMethod();
}
};
})();
moduleFn.publicMethod(); // 输出:我是私有的!
在这个例子中,privateVar和privateMethod被封装在模块内部,只能通过publicMethod访问。
2.4 防抖
防抖的本质需求是:在多次触发事件时,能够记住上一次定时器(timer),并且在新的触发时清除掉旧的定时器。
function antishake(fn, wait) {
let timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
fn();
}, wait);
};
}
let an = antishake(function() {
console.log('555');
}, 2000);
document.querySelector('div').onmouseenter = () => {
an();
};
当鼠标进入div时,执行an(),也就是调用了内部函数,clearTiimeout(timer),清除之前的setTimeout,然后重新设置一个定时器。
如果鼠标在2秒内反复移动到div上,每次都会清除旧的setTimeout。
闭包让 timer 变量在多次事件触发过程中保持持久存在,能被正确读取和修改,从而实现防抖。
2.5 节流
节流的核心思想为:一段时间内,只允许执行一次函数 fn。如果在 wait 时间内多次触发事件,都不会触发函数。
function throttle(fn, wait) {
let timer = null;
return function() {
if (timer) return;
timer = setTimeout(() => {
fn();
timer = null;
}, wait);
};
}
let throttle1 = throttle(() => {
console.log('我上车了');
}, 2000);
document.querySelector('div').onclick = () => {
throttle1();
};
节流需要记录“上一次触发时间”或“是否还在等待中”,否则就无法判断下一次是否可以执行。 首次点击时 timer 是 null,允许执行函数;
设置 timer,2秒后执行函数并将 timer 设为 null;
若用户在这 2 秒内重复点击,timer 不为 null,直接 return 不执行。
所以闭包保存并共享了 timer 变量状态,保证了“时间窗”内只执行一次。
3.闭包的缺点
闭包不会导致内存泄漏。
内存泄露是指你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。
闭包里面的变量明明就是我们需要的变量(lives),凭什么说是内存泄露?。
因为 IE。IE 有 bug,IE 在我们使用完闭包之后,依然回收不了闭包里面引用的变量。
这是 IE 的问题,不是闭包的问题。
4.总结
闭包是JavaScript中一个强大的特性,它允许函数访问其创建时的词法作用域。通过闭包,可以实现数据封装、事件处理、延迟执行等多种高级功能。理解闭包的概念和工作原理对于深入掌握JavaScript至关重要。
#前端学习#