# JavaScript闭包从入门到精通,终于不用死记硬背了

1 阅读2分钟

闭包是JavaScript中最让人困惑的概念之一。面试必问,但很多人背了定义还是不太理解。

今天用最直白的方式把闭包讲透。

一句话解释闭包

闭包就是函数记住了它被创建时的环境,即使在那个环境之外执行。

function outer() {
    let count = 0;

    function inner() {
        count++;
        console.log(count);
    }

    return inner;
}

const fn = outer();
fn(); // 1
fn(); // 2
fn(); // 3
// outer()已经执行完了,但inner还能访问count
// 这就是闭包

从作用域链理解

JavaScript有词法作用域——函数能访问定义时所在的变量,而不是调用时。

let x = 10;

function foo() {
    let y = 20;

    function bar() {
        let z = 30;
        console.log(x + y + z); // 60
        // bar能访问x(全局)、y(foo的)、z(自己的)
    }

    bar();
}

foo();

当foo返回bar的引用后,foo的执行上下文虽然被销毁了,但y变量不会被垃圾回收——因为bar还在引用它。这就是闭包的本质。

闭包的经典应用

应用一:数据私有化(模块模式)

function createCounter() {
    let count = 0; // 外部无法直接访问

    return {
        increment() { count++; },
        decrement() { count--; },
        getCount() { return count; }
    };
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
// count变量被"封闭"在闭包里,外部无法直接修改

应用二:函数工厂

function multiply(factor) {
    return function(number) {
        return number * factor;
    };
}

const double = multiply(2);
const triple = multiply(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

应用三:防抖节流(前面聊过的)

function debounce(fn, delay) {
    let timer = null; // 被闭包记住
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

闭包的经典陷阱

陷阱一:循环中的闭包

// 错误示范
for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i); // 输出5个5!
    }, 100);
}
// 因为var声明的i是函数作用域的,所有回调共享同一个i
// 等定时器执行时,循环已经结束,i=5

// 解决一:用let(块级作用域)
for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i); // 0, 1, 2, 3, 4
    }, 100);
}

// 解决二:IIFE创建闭包
for (var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j); // 0, 1, 2, 3, 4
        }, 100);
    })(i);
}

陷阱二:内存泄漏

function bindEvent() {
    const hugeData = new Array(1000000); // 大量数据

    document.getElementById('btn').onclick = function() {
        // 闭包引用了hugeData,即使按钮被移除,hugeData也不会被回收
        console.log(hugeData.length);
    };
}

解决办法:不需要时手动释放引用。

闭包和性能

闭包会让被引用的变量无法被垃圾回收,所以不要滥用。

场景是否推荐
模块模式/数据私有化推荐
函数工厂/柯里化推荐
回调中引用外部变量正常用法
大量闭包引用大对象不推荐,可能内存泄漏

闭包其实不复杂——函数记住了创建时的变量,就这么简单。关键是理解它在实际场景中的应用,以及注意内存问题。

你们在项目中哪些地方用到了闭包?