JavaScript之闭包

本文详细解析了JavaScript中闭包的概念,包括其产生过程、作用原理以及如何利用闭包隐藏私有变量,避免变量命名空间污染。同时,讨论了闭包的优缺点,帮助读者全面掌握闭包的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文一共 1300 字,读完只需 5 分钟

概述

闭包, 可以说是每个前端工程师都听说的一个词,咋一看很难从字面上去理解,从而给人留下了闭包是一个重要又难以理解的概念。

但是,闭包在 JS 代码可以说是随处可见,闭包也只是计算机领域的一个概念而已,它的存在是因为 JS 的一些语言特性,比如:函数式语言执行上下文执行上下文栈,作用域链词法作用域

执行上下文:Execution Context
执行上下文栈:Execution Context Stack
作用域链:Scope Chain
作用域:Scope

本篇文章,将先给结论,到底什么是闭包,再来分析产生闭包的过程和原因。

一、什么是闭包

当函数记住并访问所在词法作用域的自由变量时,就产生了闭包,即使函数是在当前词法作用域外执行。 --《你不知道的 JavaScript》

来段经典的闭包代码:

function outter() {
    var a = 123;
    
    function inner() {
        console.log(a);
    }
    return inner;
}

var foo = outter();
foo();  // 123
复制代码

内部函数 inner 记住了它被定义时的词法作用域,也就是 outter 的函数作用域,并访问了该作用域里的自由变量 a, 同时,inner 函数作用返回值,在外部作用域中被执行。

以上描述,全部符合闭包的描述,那这就是闭包

二、执行过程

之前的文章讲了函数的执行上下文栈,变量对象,作用域链等内容,接下来通过闭包代码回顾代码是怎么样的执行过程。

function outter() {
    var a = 123;
    
    function inner() {
        console.log(a);
    }
    return inner;
}

var foo = outter();
foo();  // 123
复制代码
  1. 进入全局代码的执行上下文,全局上下文被压入执行上下文栈。
ECStack = [
        globalContext
    ];
复制代码
  1. 全局上下文创建全局变量对象,创建 this 并指向全局上下文。
globalContext = {
    VO: global,
    scope: [global.VO],
    this: global
}
复制代码
  1. 全局上下文初始化时,outter 函数被创建,建立作用域链,复制 Scope 属性到 outter 函数的内部属性[[scope]]
  outter.[[scope]] = [     
    globalContext.VO
  ];
复制代码
  1. 执行 outter 函数,创建 outter 函数执行上下文,将 outter 上下文压入执行上下文栈。
ECStack = [
        globalContext,
        outterContext
    ];
复制代码
  1. 初始化 outter 函数执行上下文,用 arguments 创建活动对象,加入形参、函数声明、变量声明。将活动对象压入 outter 作用域链顶端。
outterContext = {
    AO: {
        arguments: {
            a: undefined,
        }
        length: 1
    },
    scope: undefined,
    inner: reference to function inner(){}
    Scope: [AO, globalContext.VO],
    this: undefined
}
复制代码
  1. outter 执行完毕,接着执行 outter 返回的被变量引用的函数 inner;
ECStack = [
        globalContext,
        innerContext
    ];
复制代码
  1. inner 函数初始化,过程和第4步一样。
innerContext = {
        AO: {
            arguments: {
                length: 0
            }
        },
        Scope: [AO, outterContext.AO, globalContext.VO],
        this: undefined
    }
复制代码
  1. inner 执行,沿着作用域链查找变量 a, 打印 a 值。
  2. inner 函数执行结束,弹出执行上下文栈。
ECStack = [
        globalContext
    ];
复制代码

在这个过程中,第 5 步,outter 已经执行结束,执行上下文按理来说已经被销毁,内部函数 inner 怎么还能访问 outter 作用域的变量呢。

正是由于闭包,inner 引用了它所在词法作用域的自由变量 a,inner 的作用域链中仍然是完整的, 尽管 inner 在其他地方执行,还是返回了正确结果。

三、函数式语言

闭包中,一个很重要的特点就是,内部函数作为一个数据被返回。这是由于 JS 是函数式语言,函数可以作为参数传递进函数,也可以作为一个数据返回。函数的嵌套构成了作用域的嵌套,也就有了作用域链。

由于函数具有作用域,且变量的寻找具有 “遮蔽效应”(从内到外,找到第一个就停止),使得局部作用域的变量对于外部作用域是不可见的,于是函数就有了封闭性,所以我们拿函数来包裹封装私有变量,同时也有了闭包。

四、自由变量

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

function outter() {
    var a = 123;
    
    function inner() {
        console.log(a);
    }
    return inner;
}

var foo = outter();
foo();  // 123
复制代码

对于 inner 函数而言,变量 a, 不是它的函数参数,也不是它的局部变量,a 就是自由变量。

五、闭包的用处和缺点

从闭包的特点可以看出,自由变量保存在了内存中,并能间接访问。

那么闭包的作用就是:

隐藏私有变量,解决变量命名空间污染的问题。

缺点
如果闭包过多,变量常驻内存,肯定会占用大量内存空间。

总结

由于 JS 是函数式语言,当函数记住并访问所在词法作用域的自由变量时,就产生了闭包,即使函数是在当前词法作用域外执行。

闭包在 JS 代码中非常常见,不必把它想得太玄乎。

欢迎关注我的个人公众号“谢南波”,专注分享原创文章。

掘金专栏 JavaScript 系列文章
  1. JavaScript之变量及作用域
  2. JavaScript之声明提升
  3. JavaScript之执行上下文
  4. JavaScript之变量对象
  5. JavaScript原型与原型链
  6. JavaScript之作用域链
  7. JavaScript之闭包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值传递
  11. JavaScript之例题中彻底理解this
  12. JavaScript专题之模拟实现call和apply
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值