当前位置: 首页 > news >正文

闭包的定义和应用场景

一、闭包是什么?

闭包是指函数可以“记住”并访问它定义时的词法作用域,即使这个函数在其作用域链之外执行。
简单说:函数 A 在函数 B 中被定义,并在函数 B 外部被调用,它依然能访问函数 B 中的变量,这就是闭包。

示例:

function outer() {let count = 0;return function inner() {count++;console.log(count);};
}const counter = outer();  // outer 执行,返回 inner
counter(); // 1
counter(); // 2
  • counter 是 inner 函数。
  • 虽然 outer 已经执行完毕,但 inner 仍能访问 outer 中的变量 count。
  • 因为 JS 引擎保留了 outer 的词法作用域 —— 这就是闭包。

二、闭包的核心特性

  1. 函数嵌套函数
  2. 内部函数引用了外部函数的变量
  3. 外部函数执行后,其内部作用域仍被保留

三、常见的闭包封装类型

1. 封装私有变量

  • 避免全局污染,创建私有作用域,保护变量不被外部修改。

示例:

function createCounter() {let count = 0;return {increment() {count++;console.log(count);},decrement() {count--;console.log(count);}};
}const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1

2. 防抖函数(Debounce)

  • 控制函数的执行频率,避免频繁触发(如搜索输入框)

示例:

function debounce(fn, delay) {let timer = null;return function (...args) {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, delay);};
}

实际应用场景:

  1. 搜索输入联想(autocomplete)
    • 描述:用户输入关键词时,实时发送接口请求搜索建议;
    • 问题:用户每打一个字就触发请求,接口压力大;
    • 防抖:用户停止输入一段时间(如 300ms)后再发起请求。
<input type="text" oninput="debounceSearch(event)">const debounceSearch = debounce((e) => {searchAPI(e.target.value);
}, 300);
  1. 窗口大小调整(resize)事件
    • 描述:监听 window.onresize 调整布局;
    • 问题:调整过程中会疯狂触发;
    • 防抖:等用户停止调整再触发逻辑。
window.addEventListener('resize', debounce(() => {updateLayout();
}, 200));
  1. 表单校验(输入完成后校验)
    • 输入过程中不断校验字段太频繁;
    • 防抖校验:等用户停止输入后再校验格式/是否重复。

3. 节流函数(Throttle)

  • 限制函数在某段时间内只执行一次,常用于滚动/resize等事件。

示例:

function throttle(fn, delay) {let lastTime = 0;return function (...args) {const now = Date.now();if (now - lastTime > delay) {fn.apply(this, args);lastTime = now;}};
}

实际应用场景:

  1. 页面滚动事件(scroll)
    • 描述:滚动时触发监听函数,计算位置、懒加载、吸顶等;
    • 问题:滚动过程中触发频繁,影响性能;
    • 节流:限制函数每隔 100ms 触发一次。
window.addEventListener('scroll', throttle(() => {checkLoadMore();
}, 100));
  1. 按钮点击防止重复提交
    • 描述:用户频繁点击“提交”按钮;
    • 节流:按钮点击1秒内只能触发一次。
<button onclick="throttleSubmit()">提交</button>const throttleSubmit = throttle(() => {submitForm();
}, 1000);
  1. 拖拽事件
    • 拖动一个 DOM 元素时,mousemove 触发频繁;
    • 节流避免频繁 DOM 操作。

防抖和节流小结

技术关键词作用
防抖(debounce)最后一次等用户停止操作一段时间后再执行
节流(throttle)每隔一次控制函数在一定时间内最多执行一次

4. 缓存函数(记忆函数)

  • 对重复计算进行缓存优化(如递归计算斐波那契数)

示例:

function memoize(fn) {const cache = {};return function (key) {if (cache[key] !== undefined) {return cache[key];}const result = fn(key);cache[key] = result;return result;};
}const fib = memoize(function(n) {console.log("计算 fib(" + n + ")");if (n <= 1) return n;return fib(n - 1) + fib(n - 2);
});console.log(fib(5)); // 会打印很多“计算 fib(n)”
console.log(fib(5)); // 这次会直接用缓存,打印很少

5. 单例模式封装

  • 只创建一次实例(如弹窗、全局组件)

示例:

function getSingleton(fn) {let instance;return function (...args) {if (!instance) {instance = fn.apply(this, args);}return instance;};
}const createDialog = getSingleton(() => {const div = document.createElement('div');div.innerHTML = '我是弹窗';document.body.appendChild(div);return div;
});
  • 第一次调用:instance 为 undefined,会执行 fn,并把返回值保存在 instance 中;
  • 后续调用:直接返回第一次的 instance,不再执行 fn。

6. 柯里化函数(Currying)

  • 把接受多个参数的函数,转换成一系列接受单个参数的函数。

示例:

function curry(fn) {return function curried(...args) {if (args.length >= fn.length) {return fn(...args);}return function (...next) {return curried(...args, ...next);};};
}function add(a, b, c) {return a + b + c;
}const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 也可以,输出 6
console.log(curriedAdd(1)(2, 3)); // 也可以,输出 6

7. 工厂函数(封装状态)

  • 用于组件、模块创建,管理状态

示例:

function createUser(name) {let _name = name;return {getName() {return _name;},setName(newName) {_name = newName;}};
}const user = createUser('77');
console.log(user.getName()); // 77
user.setName('88');
console.log(user.getName()); // 88

8. 延迟计算 / 延迟执行

  • 按需执行逻辑,提高性能或解决作用域问题

示例:

function lazy(fn) {let cached = null;return function () {if (cached === null) {cached = fn();}return cached;};
}const getConfig = lazy(() => {console.log('读取配置...');return { env: 'prod' };
});getConfig(); // 读取配置... 
getConfig(); // (不再重复执行)

四、闭包的注意事项

  • 内存泄漏风险:闭包会长期保留作用域链,如果引用过多变量未释放,可能导致内存问题。
  • 调试难度稍高:变量作用域不明显时,排查问题可能会变复杂。
http://www.dtcms.com/a/285779.html

相关文章:

  • [安洵杯 2019]easy_web
  • 深度学习×第10卷:她用一块小滤镜,在图像中找到你
  • DOM 文档对象模型
  • 【移动端知识】移动端多 WebView 互访方案:Android、iOS 与鸿蒙实现
  • Esbuild-新一代极速前端构建打包工具
  • 基于单片机多功能称重电子称设计
  • 前端下载文件并按GBK编码解析内容
  • C#`Array`进阶
  • Java全栈面试实录:从Spring Boot到AI大模型的深度解析
  • 现代R语言机器学习:Tidymodel/Tidyverse语法+回归/树模型/集成学习/SVM/深度学习/降维/聚类分类与科研绘图可视化
  • 135. Java 泛型 - 无界通配符
  • 【PTA数据结构 | C语言版】二叉堆的朴素建堆操作
  • 防爆手机是什么?能用普通手机改装吗?
  • 国产替代:ASP4644在电信通信设备中的测试与应用前景
  • 上网行为管理-web认证服务
  • Kotlin封装
  • JVM常用运行时参数说明
  • 机器人行业10年巨变从协作机器人到具身智能的萌芽、突破和成长——从 Automatic慕尼黑10 年看协作机器人到具身智能的发展
  • 基于单片机汽车驾驶防瞌睡防疲劳报警器自动熄火设计
  • Git--本地仓库的学习
  • 深入解析Linux系统启动全流程
  • 【Leecode 随笔】
  • 系统分析师-计算机系统-指令系统多处理机系统
  • 【案例教程】基于现代R语言【Tidyverse、Tidymodel】的机器学习方法与案例分析实践技术应用
  • 如何将 iPhone 备份到笔记本电脑?
  • mac mlx大模型框架的安装和使用
  • Web前端入门:JavaScript async await 的异步任务进化之路
  • 深入解析文本分类技术全景:从特征提取到深度学习架构
  • 【项目】MCP+GraphRAG搭建检索增强智能体
  • -lstdc++与-static-libstdc++的用法和差异