当前位置: 首页 > news >正文

JavaScript之闭包

本文一共 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

相关文章:

  • 秒懂sql intersect
  • VUE多项目间跳转保存用户解决方法
  • [Nuget]使用Nuget管理工具包
  • 一名QA的碎碎念
  • [Python] 输入与输出
  • Android Activity生命周期详解
  • Python+Appium自动化环境搭建
  • Unity3D之Legacy动画系统学习笔记
  • 联想关键业务服务器 sysytem X3850 X6 4U机架式服务器
  • mysql 字符集乱码及解决方案
  • android搜索框列表布局,流程及主要步骤思维导图
  • gcc介绍及安装
  • java 中获得 资源文件方法
  • IF
  • oracle导出、导入的一次实战
  • [数据结构]链表的实现在PHP中
  • 78. Subsets
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • java8-模拟hadoop
  • JAVA多线程机制解析-volatilesynchronized
  • Java方法详解
  • node-glob通配符
  • React的组件模式
  • 后端_MYSQL
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 聊一聊前端的监控
  • 入门级的git使用指北
  • 收藏好这篇,别再只说“数据劫持”了
  • 算法-图和图算法
  • 微信开源mars源码分析1—上层samples分析
  • gunicorn工作原理
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • UI设计初学者应该如何入门?
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • ​MySQL主从复制一致性检测
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (2020)Java后端开发----(面试题和笔试题)
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (考研湖科大教书匠计算机网络)第一章概述-第五节1:计算机网络体系结构之分层思想和举例
  • (强烈推荐)移动端音视频从零到上手(上)
  • (一)Spring Cloud 直击微服务作用、架构应用、hystrix降级
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (转)Linq学习笔记
  • (转)编辑寄语:因为爱心,所以美丽
  • (转)程序员技术练级攻略
  • (转载)OpenStack Hacker养成指南
  • .gitignore文件_Git:.gitignore
  • .NET Compact Framework 多线程环境下的UI异步刷新
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .NET delegate 委托 、 Event 事件
  • .net通用权限框架B/S (三)--MODEL层(2)
  • .one4-V-XXXXXXXX勒索病毒数据怎么处理|数据解密恢复
  • @RestController注解的使用
  • [22]. 括号生成