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

相关推荐
_r0bin_1 小时前
前端面试准备-7
开发语言·前端·javascript·fetch·跨域·class
IT瘾君1 小时前
JavaWeb:前端工程化-Vue
前端·javascript·vue.js
zhang98800001 小时前
JavaScript 核心原理深度解析-不停留于表面的VUE等的使用!
开发语言·javascript·vue.js
potender1 小时前
前端框架Vue
前端·vue.js·前端框架
站在风口的猪11082 小时前
《前端面试题:CSS预处理器(Sass、Less等)》
前端·css·html·less·css3·sass·html5
程序员的世界你不懂2 小时前
(9)-Fiddler抓包-Fiddler如何设置捕获Https会话
前端·https·fiddler
MoFe12 小时前
【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)
java·前端·.netcore
去旅行、在路上3 小时前
chrome使用手机调试触屏web
前端·chrome
Aphasia3113 小时前
模式验证库——zod
前端·react.js
lexiangqicheng4 小时前
es6+和css3新增的特性有哪些
前端·es6·css3