闭包的详细解读

发布于:2025-07-12 ⋅ 阅读:(19) ⋅ 点赞:(0)

闭包,每次准备面试就要来背背,争取一篇文章讲清楚。

目录

闭包的概念

作用域

全局作用域

 函数作用域 

 块级作用域

词法作用域(静态作用域)

作用域链

闭包的作用

闭包的常见应用场景

闭包的注意事项

闭包的底层原理


闭包的概念

闭包(Closure)是指有权访问另一个函数作用域中变量的函数。

闭包的形成通常发生在嵌套函数中,内层函数可以访问外层函数的变量,即使外层函数已经执行完毕。

闭包的核心是词法作用域(Lexical Scope),即函数在定义时就能记住其所在的上下文环境。

上述概念里出现了函数作用域和词法作用域,先细讲一下作用域。

作用域

即变量(上下文)或者函数生效(能被访问)的区域或者集合,换句话说,作用域决定了代码区块中变量和其他资源的可见性。

我们一般将作用域分成:

  • 全局作用域
  • 函数作用域 
  • 块级作用域

全局作用域

 全局作用域是 JavaScript 中最外层的作用域,变量或函数在全局作用域中声明时,可以在代码的任何地方访问。全局变量会成为全局对象(如浏览器中的 window)的属性。

任何不在函数中或是大括号中声明的变量,都是在全局作用域下,这些变量可以在程序的任何位置访问 

var globalVar = "I'm global";
console.log(window.globalVar); // 输出: "I'm global"

 函数作用域 

函数作用域是指在函数内部声明的变量或函数,只能在函数内部访问。使用 var 声明的变量具有函数作用域。

函数作用域也叫局部作用域,如果一个变量是在函数内部声明的,她就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问

function test() {
    var funcVar = "I'm inside a function";
    console.log(funcVar); // 输出: "I'm inside a function"
}
console.log(funcVar); // 报错: funcVar is not defined

 块级作用域

块级作用域由 let 和 const 声明引入,变量仅在 {} 定义的代码块内有效。

ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中,在大括号之外不能访问

let和const没有变量提升(var 声明的变量和函数声明会被提升至作用域顶部,但赋值不会提升。let 和 const 存在暂时性死区(TDZ),声明前访问会报错

if (true) {
    let blockVar = "I'm block-scoped";
    console.log(blockVar); // 输出: "I'm block-scoped"
}
console.log(blockVar); // 报错: blockVar is not defined

词法作用域(静态作用域)

 JavaScript 采用词法作用域,变量的作用域由代码的书写位置决定,而非运行时调用位置。

 变量被创建时就确定好了,而非执行阶段确定的。也就是说我们写好代码时它的作用域就确定了,Javascript遵循的就是词法作用域

// 由于Javascript遵循词法作用域,相同层级的foo和bar就没有办法访问到彼此函数作用域中的变量
var a = 1;
function foo(){
    console.log(a)
}
function bar(){
    var a = 2
    foo()
}
bar(); //输出1

作用域链

 作用域链是 JavaScript 查找变量的机制,从当前作用域开始逐级向外层作用域查找,直至全局作用域。

如果在全局作用域里仍然找不到该变量,它会在全局范围内隐式声明该变量(非严格模式下)或是直接报错

var global = "Global";
function outer() {
    var outerVar = "Outer";
    function inner() {
        console.log(outerVar); // 输出: "Outer"
        console.log(global);   // 输出: "Global"
    }
    inner();
}
outer();

 闭包是函数与其词法作用域的组合,即使函数在定义的作用域之外调用,仍能访问其词法作用域的变量。

function outer() {
  let count = 0;
  return function inner() {
    count++;
    console.log(count);
  };
}
const closureFn = outer(); // 形成闭包
closureFn(); // 输出 1
closureFn(); // 输出 2

闭包的作用

  1. 保存状态:闭包可以保存函数执行时的变量状态,实现类似私有变量的效果。
  2. 模块化:通过闭包封装私有变量和方法,避免全局污染。
  3. 延迟执行:闭包可以延迟变量的生命周期,例如在事件回调或异步任务中使用。

一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在的词法环境依然存在,以达到延长变量的生命周期。

闭包的常见应用场景

  1. 计数器:通过闭包实现计数器的状态保存。
  2. 私有变量:模拟面向对象中的私有成员。
  3. 函数工厂:动态生成具有特定行为的函数。
// 示例
function createCounter() {
  let privateCount = 0;
  return {
    increment: () => privateCount++,
    getCount: () => privateCount,
  };
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1

闭包的注意事项

  1. 内存泄漏:闭包会长期持有对外部变量的引用,可能导致内存无法释放。
  2. 性能影响:过度使用闭包可能增加内存消耗和执行时间。
  3. 变量共享:在循环中创建闭包时,需注意变量共享问题(通常用 IIFE 或 let 解决)。
// 循环中的闭包问题及解决
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出 3, 3, 3
}
// 解决方案:使用 IIFE 或 let
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出 0, 1, 2
}

闭包的底层原理

闭包的实现依赖于 JavaScript 的作用域链机制。当函数被创建时,会生成一个包含其所在作用域链的闭包对象(Closure)。即使外层函数执行完毕,其变量对象仍被内层函数引用,因此不会被垃圾回收。

通过理解闭包的概念、应用和注意事项,可以更高效地利用闭包解决实际问题,同时避免常见的陷阱。


网站公告

今日签到

点亮在社区的每一天
去签到