主播最近刷了一遍八股文,复盘模拟面试的时候发现有很多问题只是大概明白有这么个事情但是不能用语言准确地描述出来。因此开了这个专栏,意在整理一些前端面试高频考点及回答策略。
什么是闭包
函数套函数,在内部函数 中访问外部函数中的变量 ,二者绑定在一起。即使外部函数已经执行完毕,内部函数也可以保持对外部函数执行上下文的引用,避免GC回收掉。
闭包有两个特点:
- 创建私有变量
- 延长变量的生命周期
闭包的形成有两个条件:
- 闭包是在函数被调用执行的时候才被确认创建的
- 闭包的形成与作用域链的访问顺序有直接关系,只有内部函数访问了上层作用域链的变量时才会形成闭包 举一个最简单的例子
js
function outer() {
let i = 0;
function inner() {
console.log(i);
}
}
使用场景
封装私有变量
根据闭包的特点,可以利用闭包来封装私有变量,这样外部可以使用这个变量但无法修改。
js
function creatCounter() {
let count = 0;
return {
increment: () => ++count,
getValue: () => count
}
}
const counter = creatCounter();
console.log(counter.increment()); // 1
柯里化
闭包可以记住已经传入的参数,避免重复传相同参数,实现参数的复用
js
function getArea(width) {
return height => width * height;
}
const getTenWidthArea = getArea(10)
const aera1 = getTenWidthArea(5) // 50
既然提到了柯里化,就来寿司一下
柯里化是函数式编程里的概念,指的是固定多参数函数的一些参数,返回接收该函数剩余参数的一个新函数;如果参数总数达到要求,没有剩余参数,才调用。
一个经过柯里化的函数,参数可以分多次传入,并且可以在参数不足的情况下延迟执行。
js
// 将一个函数柯里化
function curry(fn) {
// 将函数已获得的参数个数与原函数所需参数个数进行比较
return function curried(...args) {
// 如果参数个数大于等于原函数的参数个数,则直接调用原函数
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// 否则,返回一个新函数,用于接收剩余的参数
else {
return function (...args2) {
return curried.apply(this, args.concat(args2));
}
}
}
}
var add = (a, b, c) => a + b + c
const curriedAdd = curry(add)
curriedAdd(1)(2)(3) // 6
curriedAdd(1, 2)(3) // 6
curriedAdd(1, 2, 3) // 6
防抖节流
防抖 :事件被触发n秒后再执行回调,如果期间又被触发则重新计时。类似打王者的回城操作
本质就是,连续触发时,最后一次操作后等待规定时间再执行。使用场景比如搜索联想时节约请求资源、频繁调整窗口大小时只计算最后一次的布局。
js
// 参数是回调函数和要等待的时间间隔,返回经过防抖化的函数
function debounce(func, wait) {
let timer = null // 闭包保存定时器
return (...args) => {
if (timer) clearTimeout(timer) // 清除之前的定时器
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
节流 :规定时间内多次触发,只执行一次。类似射击游戏控制射击频率。
有两种版本,分别是c触发后立即执行和延迟n秒再执行。应用场景比如监听滚动事件加载更多、鼠标单位时间内多次点击。
js
// 定时器版本,延迟执行
function throttle(func, wait) {
let timer = null // 闭包保存定时器
return (...args) => {
if (timer) return // 如果有定时器,不再执行直接返回
timer = setTimeout(() => { // 无定时器创建定时器
func.apply(this, args)
timer = null // 执行后重置定时器
}, wait)
}
}
// 时间戳版本,立即执行
function throttle(func, wait) {
let prev = 0 // 闭包保存上一次执行时间
return (...args) => {
let now = Date.now()
if (now - prev >= wait) { // 如果超过时间间隔立即执行
func.apply(this, args)
prev = now // 执行后重置上一次执行时间
}
}
}
副作用
闭包在处理速度和内存消耗方面对脚本性能有负面影响。可能导致内存泄漏,长期引用外部变量可能导致内存无法释放,需要手动解除引用。
可以在闭包内部设置手动释放闭包资源的release方法,在结束对变量的引用后调用closure.release()
(不是必须的,如果未暴露内部变量就不用操作这一步),并显式解除闭包本身的引用closure = null;
(必须的)