一文弄懂javaScript闭包

147 阅读4分钟

闭包(Closure)是指在JavaScript中,一个函数可以访问并保存其外部函数的变量、参数和作用域链,即使外部函数已经执行完毕,这个函数依然可以引用这些变量。

要理解闭包,首先需要了解JavaScript的函数作用域以及词法环境(Lexical Environment)的概念。

  1. 函数作用域(Function Scope):在JavaScript中,每个函数都会创建一个自己的作用域。在函数内定义的变量和函数参数只能在函数内部访问,而在函数外部无法直接访问。
  2. 词法环境(Lexical Environment):每个函数都有一个关联的词法环境,它包含了函数的变量、参数以及对外部词法环境的引用。当函数被调用时,会创建一个新的词法环境,并将其与函数关联起来。

闭包的实现原理如下:

  1. 在外部函数中定义一个内部函数。
  2. 内部函数可以访问外部函数的变量和参数,因为内部函数的词法环境包含了对外部函数作用域的引用。
  3. 外部函数执行完毕后,通常会返回内部函数作为结果,或者将内部函数赋值给其他变量。
  4. 内部函数形成了闭包,它可以继续引用并访问外部函数的变量和参数,即使外部函数已经执行完毕。

闭包的应用场景有很多,其中一些常见的包括:

  1. 封装私有变量:通过使用闭包,可以创建具有私有变量的对象、类或模块,并提供对外部的受控访问接口。
  2. 延迟执行:使用闭包可以实现延迟执行某个函数或回调,保存并传递特定的上下文和参数。
  3. 函数工厂:通过闭包可以动态生成函数,每个函数都有自己独立的作用域和状态。
  4. 事件处理程序:闭包可以用于创建事件处理程序,保持对事件发生时的上下文和状态的引用。

下面是一个示例代码,演示了闭包的应用:

function outerFunction(outerValue) {
    function innerFunction(innerValue) {
        console.log(outerValue + innerValue);
    }

    return innerFunction;
}

var closure = outerFunction(10); // 在outerFunction中传入参数,并返回innerFunction
closure(5); // 输出15,内部函数引用了外部函数的变量

var anotherClosure = outerFunction(20); // 创建另一个闭包
anotherClosure(5); // 输出25,不同的闭包可以保存不同的变量值


闭包在 JavaScript 中有许多实际应用场景。以下列举几个常见的应用场景:

  1. 封装私有变量和方法:通过使用闭包,可以创建具有私有变量和方法的对象或模块。外部无法直接访问内部函数中定义的变量和方法,只能通过返回的公共接口访问。
function createCounter() {
    let count = 0;

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

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 输出 2
  1. 延迟执行和函数柯里化:闭包可以用于创建延迟执行的函数或实现函数柯里化(Currying)。通过保存特定的上下文和参数,可以在稍后的时间点调用函数或传递其他参数。
function delayExecution(func, delay) {
    return function() {
        setTimeout(func, delay);
    };
}

function greet(name) {
    console.log(`Hello, ${name}!`);
}

const delayedGreet = delayExecution(greet, 2000);
delayedGreet("Alice"); // 延迟2秒后输出 "Hello, Alice!"

function add(x) {
    return function(y) {
        return x + y;
    };
}

const add5 = add(5);
console.log(add5(3)); // 输出 8
  1. 循环中的异步问题解决:在循环中使用闭包可以解决异步问题,保持对循环变量的正确引用。
function add(x) {
    return function(y) {
        return x + y;
    };
}

const add5 = add(5);
console.log(add5(3)); // 输出 8


for (var i = 0; i < 5; i++) {
    setTimeout((function(num) {
        return function() {
            console.log(num);
        };
    })(i), 1000);
}
  1. 模块化开发:通过使用闭包,可以实现模块化开发,将相关的功能和数据封装在一个闭包内,避免全局命名冲突,并提供对外部的公共接口
var module = (function() {
    var privateVariable = "私有变量";

    function privateMethod() {
        console.log("私有方法");
    }

    return {
        publicMethod: function() {
            console.log("公共方法");
        },
        publicVariable: "公共变量"
    };
})();

module.publicMethod(); // 输出 "公共方法"
console.log(module.publicVariable); // 输出 "公共变量"

这些是闭包在实践中常见的应用场景,利用闭包可以实现更灵活、安全和模块化的代码结构。但需要注意,过度使用闭包可能会导致内存泄漏问题,因此在使用闭包时应当谨