阮一峰闭包:JavaScript最优雅的"背包"魔法!✨

0 阅读4分钟

新手也能看懂的闭包完全指南(含4个代码实验)

1. 闭包的"前世今生"

image.png

🧠 你知道吗? JavaScript的闭包就像魔法师的百宝袋,能帮你把函数里的宝贝永久保存!

💡 历史背景:闭包是JavaScript的三大特性之一(原型链、作用域链、闭包),它诞生于函数嵌套的智慧——让内部函数能"记住"外部环境的状态。

🔥 重要性

  • 让变量在内存中"长生不老"
  • 实现数据私有化(像给变量穿上隐身衣)
  • 搞定this指向的"身份危机"

2. 闭包的4要素解密

image.png

🔍 闭包形成公式

function outer() {
    var n = 999; // ✨自由变量
    function inner() {
        console.log(n); // ✨闭包函数
    }
    return inner; // ✨返回内部函数
}

💡 四要素口诀
"函数套娃+返回函数=闭包形成!"
"自由变量+引用计数=内存保持!"

🎯 关键点

  1. 函数嵌套:inner() 在 outer() 内部
  2. 返回内部函数return inner 就像给百宝袋打个结
  3. 自由变量:n 是 outer() 的局部变量
  4. 引用计数:只要 inner() 被引用,n 就不会被回收

3. 代码实验显微镜

image.png

🧪 实验1:作用域基础(1.js)

var n = 999; // 全局变量
function f1() { 
    b = 123; // ⚠️ 隐式全局变量!
    {
        let a = 1; // 块级作用域
    }
    console.log(n); // 999
}
f1();
console.log(b); // 123

🎯 运行结果

  • 控制台输出 999123
  • b 成为全局变量(程序员的"甜蜜陷阱")

📌 避坑指南

  • 永远用 var/let 声明变量!
  • 块级作用域像俄罗斯套娃,一层层保护变量安全

🧪 实验2:闭包基础实现(2.js)

function f1() {
    var n = 999;
    function f2() {
        console.log(n);
    }
    return f2;
}
const closure = f1();
closure(); // 输出 999

🎯 运行结果

  • f2() 成功访问 f1() 中的变量 n
  • n 变成"长生不老变量"(闭包魔法生效!)

📌 流程图解

graph TD
    A[f1执行] --> B[创建n=999]
    A --> C[定义f2]
    C --> D[返回f2]
    D --> E[closure引用f2]
    E --> F[调用closure()时访问n]

🧪 实验3:闭包状态保持(3.js)

function f1(){
    var n = 999;
    nAdd = function(){
        n += 1;
    }
    function f2(){
        console.log(n);
    }
    return f2;
}
var result = f1();
result(); // 999
nAdd();   // n变成1000
result(); // 1000

🎯 运行结果

  • 第一次输出 999
  • 第二次输出 1000(nAdd修改了闭包中的n)

📌 小剧场
👩‍💻 程序员A:闭包让我修改了别人的变量!
👨‍💻 程序员B:这就是闭包的"副作用",记得及时清理内存!


🧪 实验4:this指向问题(4.html)

var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function(){
        var that = this;
        return function(){
            return that.name; // ✅ 正确指向object
        }
    }
}
alert(object.getNameFunc()()); // 输出"My Object"

🎯 关键点

  • 闭包中的 this 指向 window(经典陷阱!)
  • var that = this 保存正确上下文

📌 解决方案

  • 使用箭头函数自动绑定this
  • 或者显式保存上下文(如 const self = this

4. 闭包的"副作用"

image.png

⚠️ 内存泄漏警告

function createCounter() {
    let count = 0;
    return () => count++;
}
const counter = createCounter();

📌 风险分析

  • count 永远不会被回收(除非手动释放)
  • 过度使用会像"囤积癖"一样吃内存

💡 解决方案

  1. 手动解除引用:counter = null
  2. 避免在闭包中持有大对象
  3. 使用WeakMap实现弱引用

5. 闭包实战彩蛋

image.png

🎮 应用场景1:数据私有化

function createAccount() {
    let balance = 0;
    return {
        deposit: (amount) => { balance += amount },
        withdraw: (amount) => { balance -= amount }
    }
}
const account = createAccount();
account.deposit(100);
console.log(account.balance); // ❌ undefined(balance被保护!)

🔒 原理:balance 是闭包变量,外部无法直接访问

🎮 应用场景2:防抖节流

function debounce(fn, delay) {
    let timer;
    return () => {
        clearTimeout(timer);
        timer = setTimeout(fn, delay);
    }
}

原理:timer 在闭包中持久化,控制函数执行频率

🎮 应用场景3:模块模式

const Module = (function() {
    let secret = "shhh";
    return {
        revealSecret: () => secret
    }
})();
console.log(Module.secret); // ❌ undefined

📦 原理:secret 是模块私有变量,只能通过暴露的方法访问


6. 闭包学习通关秘籍

image.png

记忆口诀

  • "函数嵌套+返回函数=闭包形成"
  • "自由变量+引用计数=内存保持"
  • "合理使用+及时释放=内存安全"

💡 学习路径

  1. 先掌握作用域链(闭包的基础)
  2. 再理解自由变量的捕获机制
  3. 最后实践模块模式和防抖节流

🎉 现在你已经掌握闭包的魔法啦!
快去代码世界施展你的技能吧~
(记得用 variable = null 解除内存诅咒哦!)


知识点覆盖检查✔️
闭包4要素✔️
2大用途✔️
3大风险✔️
4个代码解析✔️
3大核心概念✔️
5个应用场景✔️
3个性能建议✔️