🎬 HoRain云小助手:个人主页
🔥 个人专栏: 《Linux 系列教程》《c语言教程》
⛺️生活的理想,就是为了理想的生活!
⛳️ 推荐
前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。
专栏介绍
专栏名称 | 专栏介绍 |
《C语言》 | 本专栏主要撰写C干货内容和编程技巧,让大家从底层了解C,把更多的知识由抽象到简单通俗易懂。 |
《网络协议》 | 本专栏主要是注重从底层来给大家一步步剖析网络协议的奥秘,一起解密网络协议在运行中协议的基本运行机制! |
《docker容器精解篇》 | 全面深入解析 docker 容器,从基础到进阶,涵盖原理、操作、实践案例,助您精通 docker。 |
《linux系列》 | 本专栏主要撰写Linux干货内容,从基础到进阶,知识由抽象到简单通俗易懂,帮你从新手小白到扫地僧。 |
《python 系列》 | 本专栏着重撰写Python相关的干货内容与编程技巧,助力大家从底层去认识Python,将更多复杂的知识由抽象转化为简单易懂的内容。 |
《试题库》 | 本专栏主要是发布一些考试和练习题库(涵盖软考、HCIE、HRCE、CCNA等) |
目录
⛳️ 推荐
专栏介绍
🔧 形成条件与工作原理
🚀 核心应用场景
1. 数据封装与私有变量
2. 模块模式
3. 函数工厂与柯里化
4. 解决循环中的变量捕获问题
⚠️ 注意事项与性能考量
💎 总结
JavaScript 闭包确实是一个既核心又容易让人困惑的概念。简单来说,闭包就是函数与其词法环境的组合,它让内部函数能够“记住”并访问创建时的作用域,即使外部函数已经执行完毕。
为了让你快速抓住核心,我们先来看一个最经典的例子:
function createCounter() { let count = 0; // 一个局部变量 return function() { count++; // 内部函数引用了外部变量 return count; }; } const myCounter = createCounter(); console.log(myCounter()); // 1 console.log(myCounter()); // 2 console.log(myCounter()); // 3在这个例子里,count看似是createCounter的局部变量,通常函数执行完就会被清理。但由于createCounter返回了一个内部函数,并且这个内部函数引用了count,就形成了一个闭包,使得count能被持续维护和访问。
🔧 形成条件与工作原理
一个典型的闭包需要满足三个条件:
函数嵌套:一个函数内部定义了另一个函数。
引用外部变量:内部函数引用了外部函数作用域中的变量(包括参数)。
外部调用:内部函数在其外部作用域被调用(如被返回、作为回调函数传递等)。
其工作原理关键在于 JavaScript 的词法作用域 和垃圾回收机制。
词法作用域:函数在定义时就确定了它所能访问的作用域链,而不是在执行时。因此,内部函数无论在哪里被调用,都能通过这条作用域链找到定义时的外部变量。
垃圾回收:在 JavaScript 中,如果一个对象不再被任何地方引用,它就会被垃圾回收机制释放。闭包中,由于内部函数持有对外部变量的引用,所以这些变量不会被回收,得以在内存中“存活”。
🚀 核心应用场景
闭包的应用非常广泛,是其强大能力的体现。
1. 数据封装与私有变量
JavaScript 本身没有类似 Java 的private关键字来直接声明私有变量,但闭包可以完美实现这个功能。
function createPerson(name) { // `_name` 和 `_age` 可以看作是私有变量 let _name = name; let _age = 0; return { getName: function() { return _name; }, getAge: function() { return _age; }, setAge: function(age) { if (age > 0) _age = age; }, celebrateBirthday: function() { _age++; } }; } const person = createPerson('Alice'); console.log(person.getName()); // "Alice" person.setAge(25); person.celebrateBirthday(); console.log(person.getAge()); // 26 // 无法直接访问 _name 或 _age,实现了数据的封装和保护在这个例子中,外部代码无法直接修改_name或_age,必须通过返回的公共方法,这很好地体现了封装性。
2. 模块模式
闭包是早期 JavaScript 实现模块化的基石,可以有效避免全局变量污染。
const Calculator = (function() { // 私有变量和函数 let memory = 0; function add(x, y) { return x + y; } function subtract(x, y) { return x - y; } // 公开的API return { calculate: function(a, b, operator) { const result = operator === '+' ? add(a, b) : subtract(a, b); memory = result; // 可以操作私有变量 return result; }, getMemory: function() { return memory; }, clearMemory: function() { memory = 0; } }; })(); console.log(Calculator.calculate(5, 3, '+')); // 8 console.log(Calculator.getMemory()); // 8 Calculator.clearMemory();这种模式被称为“立即调用函数表达式”,它创建了一个独立的作用域,只暴露特定的接口给外部。
3. 函数工厂与柯里化
闭包可以用于创建特定配置的函数。
// 函数工厂 function createMultiplier(factor) { return function(x) { return x * factor; }; } const double = createMultiplier(2); const triple = createMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15 // 简单的柯里化 function add(x) { return function(y) { return x + y; }; } const add5 = add(5); console.log(add5(3)); // 84. 解决循环中的变量捕获问题
这是一个经典面试题,展示了var作用域和闭包的相互作用。
// 问题:所有按钮点击都会输出 buttons.length for (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i); // 输出 3, 3, 3 }, 100); } // 解决方案1:使用IIFE和闭包,为每次迭代创建独立作用域 for (var i = 0; i < 3; i++) { (function(j) { // j 捕获了当前循环的 i 值 setTimeout(function() { console.log(j); // 输出 0, 1, 2 }, 100); })(i); } // 解决方案2(现代最佳实践):使用 `let` 声明变量 for (let i = 0; i < 3; i++) { setTimeout(function() { console.log(i); // 输出 0, 1, 2 }, 100); }let声明的变量具有块级作用域,每次循环都会创建一个新的i绑定,本质上也是一种闭包行为,但写法简洁得多。
⚠️ 注意事项与性能考量
闭包虽好,但也要注意使用方式。
内存泄漏风险:因为闭包会长期持有外部变量的引用,如果闭包本身的生命周期很长(例如被赋给全局变量或作为事件监听器未移除),那么它引用的所有变量都不会被垃圾回收,可能导致内存占用过高。在不需要闭包时,主动将引用置为
null有助于垃圾回收。性能考量:闭包会延长作用域链,在查找变量时需要遍历更长的链,理论上会比局部变量查找稍慢。但在绝大多数情况下,这种差异微乎其微,不应成为放弃使用闭包的理由。通常更应关注代码的清晰度和可维护性。
💎 总结
闭包是 JavaScript 的一个强大特性,它允许函数访问并记住其词法作用域,即使函数在其作用域外执行。闭包常用于创建私有变量和实现模块化。理解闭包的关键在于掌握 JavaScript 的词法作用域和函数是一等公民的特性。
希望这些解释和示例能帮助你透彻地理解 JavaScript 闭包。如果你对某个特定应用场景或细节还有疑问,我们可以继续深入探讨。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄
💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍
🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙