闭包及其使用场景

主播最近刷了一遍八股文,复盘模拟面试的时候发现有很多问题只是大概明白有这么个事情但是不能用语言准确地描述出来。因此开了这个专栏,意在整理一些前端面试高频考点及回答策略。

什么是闭包

函数套函数,在内部函数 中访问外部函数中的变量 ,二者绑定在一起。即使外部函数已经执行完毕,内部函数也可以保持对外部函数执行上下文的引用,避免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;(必须的)

相关推荐
卸任7 小时前
Vibe coding工具使用心得
前端·ai编程
brien98 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance8 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M8 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写8 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李8 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易8 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马8 小时前
前沿视界
前端
kyriewen9 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易9 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员