js中的闭包

闭包是JS的重要特性,允许函数访问其外部作用域的变量,即使外部函数已执行完毕。本文通过实例讲解闭包的概念、作用及潜在的内存泄漏问题。

闭包是js的一个难点也是它的一个特色,是我们必须掌握的js高级特性,那么什么是闭包呢?它又有什么用呢?

我们都知道,js的作用域分两种,全局和局部,基于我们所熟悉的作用域链相关知识,我们知道在js作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,反之则不能,也就是说在外层作用域下无法获取内层作用域下的变量,同样在不同的函数作用域中也是不能相互访问彼此变量的,那么我们想在一个函数内部也有限权访问另一个函数内部的变量该怎么办呢?闭包就是用来解决这一需求的,闭包的本质就是在一个函数内部创建另一个函数。
我们首先知道闭包有3个特性:
①函数嵌套函数
②函数内部可以引用函数外部的参数和变量
③参数和变量不会被垃圾回收机制回收

本文我们以闭包两种的主要形式来学习
在这里插入图片描述

在这段代码中,a()中的返回值是一个匿名函数,这个函数在a()作用域内部,所以它可以获取a()作用域下变量name的值,将这个值作为返回值赋给全局作用域下的变量b,实现了在全局变量下获取到局部变量中的变量的值

再来看一个闭包的经典例子
在这里插入图片描述

一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,但是在这个例子中,匿名函数作为fn的返回值被赋值给了fn1,这时候相当于fn1=function(){var n = 0 … },并且匿名函数内部引用着fn里的变量num,所以变量num无法被销毁,而变量n是每次被调用时新创建的,所以每次fn1执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的num,于是这里就产生了内存消耗的问题

再来看一个经典例子-定时器与闭包

写一个for循环,让它按顺序打印出当前循环次数
在这里插入图片描述

按照预期它应该依次输出1 2 3 4 5,而结果它输出了五次5,这是为什么呢?原来由于js是单线程的,所以在执行for循环的时候定时器setTimeout被安排到任务队列中排队等待执行,而在等待过程中for循环就已经在执行,等到setTimeout可以执行的时候,for循环已经结束,i的值也已经编程5,所以打印出来五个5,那么我们为了实现预期结果应该怎么改这段代码呢?(ps:如果把for循环里面的var变成let,也能实现预期结果)

在这里插入图片描述

引入闭包来保存变量i,将setTimeout放入立即执行函数中,将for循环中的循环值i作为参数传递,100毫秒后同时打印出1 2 3 4 5

那如果我们想实现每隔100毫秒分别依次输出数字,又该怎么改呢?

在这里插入图片描述

在这段代码中,相当于同时启动3个定时器,i*100是为4个定时器分别设置了不同的时间,同时启动,但是执行时间不同,每个定时器间隔都是100毫秒,实现了每隔100毫秒就执行一次打印的效果。

②闭包作为参数传递

在这里插入图片描述

在这段代码中,函数fn1作为参数传入立即执行函数中,在执行到fn2(30)的时候,30作为参数传入fn1中,这时候if(x>num)中的num取的并不是立即执行函数中的num,而是取创建函数的作用域中的num这里函数创建的作用域是全局作用域下,所以num取的是全局作用域中的值15,即30>15,打印30

最后总结一下闭包的好处与坏处
好处

①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突

②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)

③匿名自执行函数可以减少内存消耗

坏处

①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;

②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

<think>首先,用户的问题是关于JavaScript闭包的常见示例和用法。我需要根据系统指令来构建回答。 系统指令关键点: - 所有行内数学表达式用 `$...$` 格式,但这里没有数学内容,所以可能不需要。 - 独立公式用 `$$...$$`,但用户问题不涉及数学,所以跳过。 - LaTeX语法正确:同样,不适用。 - 使用中文回答:整个回答要用中文。 - 生成相关问题:在回答结束后,添加相关问题部分。 - 回答中引用的段落末尾自然地添加引用标识,如 `[^1]`。引用来自提供的参考。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 用户提供的引用: - [^1]: 关于闭包的实际应用:封装私有变量、延迟执行、保存状态。 - [^2]: 闭包的核心特点:记住词法作用域。 - [^3]: 常见闭包示例:函数作为返回值、作为实参传递、高阶函数与柯里化。 - [^4]: 闭包的影响和实例:定时器问题。 - [^5]: 另一个示例:事件处理中的闭包。 用户问题: "我想了解JavaScript闭包的常见示例 请问JavaScript 闭包常用示例及用法" 我需要基于这些引用构建回答。参考是站内引用,我应该自然地融入回答中。 回答结构: 1. **介绍闭包**:简要解释闭包是什么,基于引用[^2]。 2. **常见示例**:详细描述常见示例,从引用[^3]和[^5]。 - 函数作为返回值(计数器示例)。 - 函数作为实参传递(setTimeout示例)。 - 事件处理(按钮点击示例)。 - 定时器问题(引用[^4]的for循环问题)。 3. **用法**:讨论闭包的用途,基于引用[^1],如封装私有变量、延迟执行、保存状态。 4. **注意事项**:提到闭包可能的影响,如性能问题(引用[^4])。 5. **结尾**:总结并添加相关问题。 确保回答清晰、逐步解释。使用中文。 在引用段落末尾添加引用标识,例如:在描述一个概念后加 `[^1]`。 生成相关问题: - 基于主题,如闭包的优缺点、如何避免内存泄漏、实际应用场景等。 大纲回答: - 开头:定义闭包。 - 主体:常见示例和用法。 - 示例1:计数器(函数返回)。 - 示例2:setTimeout。 - 示例3:事件处理。 - 示例4:定时器问题。 - 用法:封装、延迟、状态保存。 - 注意事项:性能影响。 - 结尾:总结。 - 相关问题:3-5个问题。 现在,整合引用: - 当提到封装私有变量时,引用[^1]。 - 当描述核心特点时,引用[^2]。 - 当给出示例时,引用[^3]、[^4]、[^5]。 最终输出格式: - 回答部分。 - 相关问题部分以 "§§相关问题§§" 开头。</think>### JavaScript 闭包常用示例及用法详解 在 JavaScript 中,闭包(Closure)是一个核心概念,它允许函数访问并记住其定义时的词法作用域(Lexical Scope),即使函数在其作用域外部执行闭包的核心特点包括:函数能“捕获”外部变量,并保持对这些变量的引用,从而在函数执行时访问或修改它们[^2]。闭包JavaScript 中有广泛的实际应用,例如封装私有变量、实现延迟执行或保存状态。下面我将逐步解释闭包的常见示例和用法,帮助您深入理解。 #### 一、闭包的常见示例 闭包的常见应用场景可以通过具体代码示例来展示。这些示例基于实际开发中的高频用例,确保您能直观掌握闭包的运作机制。 1. **函数作为返回值(计数器示例)** 这是闭包最经典的用法之一,常用于创建私有变量或状态管理。例如,定义一个函数返回另一个函数,内部函数访问并修改外部函数的变量: ```javascript function createCounter() { let count = 0; // 私有变量,通过闭包访问 return function() { count++; console.log(count); }; } const counter = createCounter(); counter(); // 输出: 1 counter(); // 输出: 2 ``` 在这个示例中,`createCounter` 函数返回的匿名函数形成了一个闭包,它“记住”了 `count` 变量,每次调用都会递增并输出其值。这实现了数据的封装和状态保存[^3]。 2. **函数作为实参传递(延迟执行示例)** 闭包常用于异步操作,如 `setTimeout` 或事件处理,以保存函数执行时的上下文。例如,传递一个闭包作为回调函数: ```javascript function showMessage(msg, delay) { setTimeout(function() { console.log(msg); // 闭包捕获了 msg 参数 }, delay); } showMessage('Hello, World!', 2000); // 2秒后输出 "Hello, World!" ``` 这里,匿名函数作为 `setTimeout` 的回调,通过闭包访问了 `msg` 参数,即使 `showMessage` 函数已执行完毕。这展示了闭包如何实现延迟执行和上下文保存[^3]。 3. **事件处理(按钮点击示例)** 在 DOM 操作中,闭包常用于事件监听器,以绑定特定数据到事件回调。例如,为多个按钮添加点击事件: ```javascript function setupButtons() { for (let i = 0; i < 3; i++) { document.getElementById(`btn${i}`).addEventListener('click', function() { console.log(`Button ${i} clicked`); // 闭包捕获了循环变量 i }); } } setupButtons(); // 每个按钮点击时输出对应索引 ``` 使用 `let` 声明循环变量 `i`,确保每个闭包捕获独立的 `i` 值。如果使用 `var`,会导致所有按钮输出相同值(如 `Button 3 clicked`),这是因为 `var` 的作用域问题。闭包在这里帮助保存了事件触发时的状态[^5]。 4. **定时器中的常见问题(循环闭包示例)** 闭包在循环中可能引发意外行为,需特别注意作用域管理。例如,一个经典的定时器问题: ```javascript for (var i = 1; i <= 3; i++) { setTimeout(function() { console.log(i); // 输出: 4, 4, 4(而非预期的 1,2,3) }, 1000); } ``` 问题原因:`var i` 是全局作用域,所有闭包共享同一个 `i`(循环结束后 `i=4`)。解决方案是使用 `let` 或创建新作用域: ```javascript for (let i = 1; i <= 3; i++) { // 使用 let 创建块级作用域 setTimeout(function() { console.log(i); // 输出: 1, 2, 3 }, 1000); } ``` 这突显了闭包在异步代码中的陷阱,需合理控制变量作用域[^4]。 #### 二、闭包的常见用法 闭包不仅用于示例场景,还广泛应用于以下功能,提升代码模块化和可维护性[^1]: - **封装私有变量和方法**:闭包可以创建模块化组件,避免全局污染。例如,实现一个简单的“私有”计数器: ```javascript const counterModule = (function() { let privateCount = 0; return { increment: function() { privateCount++; }, getCount: function() { return privateCount; } }; })(); counterModule.increment(); console.log(counterModule.getCount()); // 输出: 1 ``` 这里,IIFE(立即执行函数)返回的对象方法通过闭包访问 `privateCount`,外部无法直接修改该变量[^1]。 - **延迟函数执行**:闭包保存参数和上下文,用于定时器或事件队列。例如,在 AJAX 回调中保持数据状态。 - **保存状态**:闭包使函数能“记住”先前操作,如实现状态机或缓存机制。例如,记忆化(Memoization)优化递归计算: ```javascript function memoize(fn) { const cache = {}; return function(...args) { const key = JSON.stringify(args); if (cache[key]) return cache[key]; const result = fn.apply(this, args); cache[key] = result; return result; }; } const factorial = memoize(n => n <= 1 ? 1 : n * factorial(n-1)); console.log(factorial(5)); // 计算并缓存结果 ``` #### 三、注意事项 尽管闭包功能强大,但需注意潜在问题: - **性能影响**:频繁创建闭包可能增加内存开销,因为每个闭包都捕获整个作用域链。在性能敏感场景(如高频事件),应避免不必要的闭包创建[^4]。 - **内存泄漏风险**:如果闭包引用 DOM 元素或其他大对象,可能导致垃圾回收器无法释放内存。解决方法是手动解除引用(如移除事件监听器)。 - **作用域管理**:确保正确使用 `let` 或 `const` 避免变量共享问题,如前述循环示例。 #### 总结 闭包JavaScript 的核心特性,通过示例(如计数器、事件处理和定时器)和用法(封装、延迟执行、状态保存),它实现了强大的模块化和异步处理能力。理解闭包的工作原理(词法作用域捕获)和注意事项(性能优化),能帮助您编写更健壮的代码。在实际开发中,闭包常用于框架(如 React Hooks)和库设计,提升代码复用性[^1][^2]。
评论 13
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值