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高级技巧。

相关推荐
知识分享小能手36 分钟前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
luckys.one37 分钟前
第9篇:Freqtrade量化交易之config.json 基础入门与初始化
javascript·数据库·python·mysql·算法·json·区块链
魔云连洲38 分钟前
深入解析:Vue与React的异步批处理更新机制
前端·vue.js·react.js
mCell1 小时前
JavaScript 的多线程能力:Worker
前端·javascript·浏览器
weixin_437830943 小时前
使用冰狐智能辅助实现图形列表自动点击:OCR与HID技术详解
开发语言·javascript·ocr
超级无敌攻城狮3 小时前
3 分钟学会!波浪文字动画超详细教程,从 0 到 1 实现「思考中 / 加载中」高级效果
前端
excel3 小时前
用 TensorFlow.js Node 实现猫图像识别(教学版逐步分解)
前端
gnip4 小时前
JavaScript事件流
前端·javascript
小菜全4 小时前
基于若依框架Vue+TS导出PDF文件的方法
javascript·vue.js·前端框架·json