JavaScript篇:"闭包:天使还是魔鬼?6年老司机带你玩转JS闭包"

大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个被JS闭包折磨了6年又爱又恨的前端工程师。今天我要带大家深入理解闭包这个让人又爱又恨的特性,分享那些年我踩过的坑和总结的优化技巧。

一、闭包是什么?一个简单的例子

javascript 复制代码
function outer() {
    let me = '小杨';
    return function inner() {
        console.log(`大家好,我是${me}`);
    };
}
const sayHello = outer();
sayHello(); // "大家好,我是小杨"

看到没?inner函数记住了outer函数的me变量,这就是闭包!

二、闭包的三大妙用(天使面)

1. 创建私有变量

javascript 复制代码
function createCounter() {
    let count = 0;
    return {
        increment() { count++ },
        getCount() { return count }
    };
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined

2. 实现函数柯里化

javascript 复制代码
function multiply(a) {
    return function(b) {
        return a * b;
    };
}
const double = multiply(2);
console.log(double(5)); // 10

3. 事件处理中的妙用

javascript 复制代码
function setupButtons() {
    for(var i = 1; i <= 3; i++) {
        (function(index) {
            document.getElementById(`btn-${index}`)
                .addEventListener('click', function() {
                    console.log(`我是按钮${index}`);
                });
        })(i);
    }
}

三、闭包的三大坑(魔鬼面)

1. 内存泄漏

javascript 复制代码
function leakMemory() {
    const bigData = new Array(1000000).fill('*');
    return function() {
        console.log('我还记得bigData');
    };
}
const leaked = leakMemory();
// bigData本应该被回收,但闭包让它一直存在

2. 性能问题

javascript 复制代码
function slowPerformance() {
    const data = {}; // 大对象
    return function(key, value) {
        data[key] = value;
        // 每次调用都要访问闭包变量
    };
}

3. 意外的变量共享

javascript 复制代码
function createFunctions() {
    let funcs = [];
    for(var i = 0; i < 3; i++) {
        funcs.push(function() {
            console.log(i); // 全是3!
        });
    }
    return funcs;
}

四、闭包优化六大法则(6年经验总结)

1. 及时释放引用

javascript 复制代码
function createHeavyObject() {
    const heavy = new Array(1000000).fill('*');
    return {
        useHeavy: function() {
            // 使用heavy
        },
        cleanup: function() {
            heavy = null; // 手动释放
        }
    };
}

2. 使用块级作用域

javascript 复制代码
// 修复前面的共享变量问题
function createFixedFunctions() {
    let funcs = [];
    for(let i = 0; i < 3; i++) { // 使用let
        funcs.push(function() {
            console.log(i); // 0,1,2
        });
    }
    return funcs;
}

3. 避免不必要的闭包

javascript 复制代码
// 不好的写法
function unneededClosure() {
    const data = {};
    return function() {
        // 根本不使用data,却形成了闭包
        console.log('Hello');
    };
}

// 好的写法
function noClosure() {
    console.log('Hello');
}

4. 使用WeakMap管理私有变量

javascript 复制代码
const privateData = new WeakMap();

class MyClass {
    constructor() {
        privateData.set(this, {
            secret: '我是私有数据'
        });
    }
    
    getSecret() {
        return privateData.get(this).secret;
    }
}

5. 合理使用IIFE

javascript 复制代码
// 立即执行函数减少闭包生命周期
(function() {
    const tempData = processData();
    // 使用tempData
})(); // 执行完立即释放

6. 使用模块化

javascript 复制代码
// 模块化天然适合管理闭包
const counterModule = (function() {
    let count = 0;
    
    return {
        increment() { count++ },
        getCount() { return count }
    };
})();

五、真实案例分享

案例1:我曾经在项目中遇到一个页面卡顿问题,最后发现是因为一个事件处理函数形成了闭包,持有了一个大DOM树的引用。解决方案是:

javascript 复制代码
// 修复前
function setup() {
    const bigElement = document.getElementById('big');
    button.addEventListener('click', function() {
        // 持有bigElement引用
        console.log(bigElement.id);
    });
}

// 修复后
function setup() {
    const id = 'big';
    button.addEventListener('click', function() {
        // 只存储需要的id
        console.log(id);
    });
}

六、总结

闭包就像一把双刃剑:

✅ 优点:实现私有变量、函数柯里化、模块化等

❌ 缺点:可能导致内存泄漏、性能问题

记住我的6年经验总结:

  1. 及时释放不再需要的引用
  2. 优先使用块级作用域
  3. 避免不必要的闭包
  4. 合理使用WeakMap和模块化
  5. 善用开发者工具检查内存

最后留个思考题:

javascript 复制代码
function createFunctions() {
    let funcs = [];
    for(var i = 0; i < 3; i++) {
        funcs.push(function(j) {
            return function() {
                console.log(j);
            };
        }(i));
    }
    return funcs;
}
const funcs = createFunctions();
funcs[0](); // 输出什么?为什么?

欢迎在评论区讨论你的答案!下期我会分享更多JS高级技巧。

相关推荐
程序员小寒3 小时前
JavaScript设计模式(八):命令模式实现与应用
前端·javascript·设计模式·ecmascript·命令模式
wgod3 小时前
new AbortController()
前端
UXbot3 小时前
UXbot 是什么?一句指令生成完整应用的 AI 工具
前端·ai·交互·个人开发·ai编程·原型模式·ux
棒棒的唐4 小时前
WSL2用npm安装的openclaw,无法正常使用openclaw gateway start启动服务的问题
前端·npm·gateway
哔哩哔哩技术4 小时前
使用Compose Navigation3进行屏幕适配
前端
Z_Wonderful5 小时前
在 Next.js 中,使用 [id] 或 public 作为文件夹或文件名是两种完全不同的概念,分别对应 动态路由 和 静态资源托管
javascript·网络·chrome
咬人喵喵5 小时前
E2.COOL 平台深度解析:从特效分类到实战操作指南
前端·编辑器·svg
RisunJan5 小时前
Linux命令-named-checkzone
linux·前端
小陈工5 小时前
Python Web开发入门(十):数据库迁移与版本管理——让数据库变更可控可回滚
前端·数据库·人工智能·python·sql·云原生·架构
吹晚风吧6 小时前
解决vite打包,base配置前缀,nginx的dist包找不到资源
服务器·前端·nginx