前言
如今这年头早已不是靠八股文就能横扫求职战场的年代了,所有的问题都应该会归于项目中,闭包问题作为一个老生常谈的问题,在面试中屡见不鲜,然而我们往往很容易忽视这个问题在项目中的使用,一味的八股文恐怕很难获得面试官的青睐~
1.数据封装和信息隐蔽
创建私有变量,这些变量无法被外部直接访问。这是实现模块化或者构造函数中数据隐蔽的方式。也是最常见的闭包使用场景:
function createCounter() {
let count = 0;
return function increment() {
count ++;
return count;
}
}
const fn = createCounter();
console.log(fn()); // 1
console.log(fn()); // 2
console.log(fn()); // 3
2.缓存函数
函数能够缓存记忆先前的计算结果,减少计算从而优化性能
function memorize(fn) {
let cache = {}; // 定义对象进行缓存数据的存储
return function(...args) {
console.log('获取参数', args)
let n = args[0];
if(n in cache) {
console.log('从缓存中取值', cache[n])
return cache[n];
} else {
let result = fn(n);
cache[n] = result;
console.log('执行计算取值', result)
return result;
}
}
}
function getInfn(num) {
console.log('执行复杂运算',)
return num * 2000;
}
const cacheFn = memorize(getInfn);
cacheFn(3);
cacheFn(3);
cacheFn(3);
cacheFn(3);
// 输出结果
取参数 [ 3 ]
执行复杂运算
执行计算取值 6000
获取参数 [ 3 ]
从缓存中取值 6000
获取参数 [ 3 ]
从缓存中取值 6000
获取参数 [ 3 ]
从缓存中取值 6000
3. 事件处理器中维持状态
用于在不同的事件调用中保存状态
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="myBtn">点我吧哈哈</button>
</body>
<script>
function eventFn() {
let clickTimes = 0;
document.getElementById('myBtn').addEventListener('click', () => {
clickTimes ++;
console.log('记录点击次数', clickTimes)
})
}
eventFn()
</script>
</html>
4. 函数的防抖和节流
必包实现节流函数 —— 在一定时间范围内,只执行第一次函数回调
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流</title>
<body>
<button id="btn">点我试试</button>
<script>
const btn = document.querySelector("#btn");
// 要求: 3s内只输出一次hi
function sayHi() {
console.log("hi")
}
// 在一定时间范围内,只执行第一次函数回调
function throttle(fn, wait = 3000) {
let flag = true;
return () => {
if (flag) {
flag = false;
fn();
setTimeout(() => {
flag = true;
}, wait)
}
}
}
btn.addEventListener("click", throttle(sayHi, 3000))
</script>
</body>
</html>
防抖 —— 在一定时间范围内,只执行最后一次函数回调
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖</title>
</head>
<body>
<button id="btn">点我试试</button>
<script>
const btn = document.querySelector("#btn")
function sayHi() {
console.log("Hi");
}
// 在一定时间范围内,只执行最后一次函数回调
function debounce(fn, delay) {
let timer = null;
return () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn();
}, delay);
}
}
btn.addEventListener("click", debounce(sayHi, 1000));
</script>
</body>
</html>
5. 函数柯里化
通过闭包记录每次调用的参数,并最终累计求值
function sum(a) {
return function(b) {
return b ? sum(a + b) : a;
}
}
console.log(sum(1)(2)(3)(4)())
6.维护链式调用的状态
闭包在构建支持链式调用的库中很有用,例如 jQuery 中,可以在调用链中维持状态
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<h3 class="selector">hello</h3>
<h3 class="selector">world</h3>
</div>
</body>
<script>
// 闭包实现一个链式调用的函数
function query(selector) {
let ele = document.querySelectorAll(selector);
return {
hide: function() {
ele.forEach(val => val.style.display = 'none');
return this;
},
show: function() {
ele.forEach(val => val.style.display = '');
return this;
}
}
}
let time = 0;
// 定时器累积为偶数时隐藏,为奇数时显示
setInterval(() => {
if(time % 2 === 0) {
query('.selector').hide();
} else {
query('.selector').show();
}
time ++;
}, 2000)
</script>
</html>
7.利用立即执行函数和闭包实现模块化,封装私有状态和方法
const myModule = (function() {
let _var = "private value";
function privateFn() {
console.log(_var);
}
return {
publicMethod: function() {
privateFn()
}
}
})()
myModule.publicMethod();