一篇文章帮你真正理解javascsript作用域闭包

简介: 一篇文章帮你真正理解javascsript作用域闭包

前言

      关于javascript闭包其实已经是老生常谈了,我很久以前也写过有关闭包的文章,前段时间面试字节跳动的时候,面试官笑着和我说,闭包的内容可以再看看,以前我对闭包的认识和理解也是狭隘的,闭包是这门语言中极其难以掌握又非常重要的概念,这一篇文章的意义旨在帮大家真正理解javascript闭包


正文

javascript闭包无处不在

      闭包是基于词法作用域书写代码时所产生的自然结果,你甚至不需要为了利用它们而有意识地创建闭包。闭包的创建和使用在你的代码中随处可见。你缺少的是根据你自己的意愿来识别、拥抱和影响闭包的思维环境。为什么这么说,后面我会进一步举例说明


闭包的实质

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

      很多文章把闭包解释为定义在函数里的函数,其实在一定意义上是不那么准确的,闭包真正的理解是,能给予在外部作用域访问它本不能访问到的作用域的能力,比如看一下下面的例子:

640.png

      这是闭包吗?技术上来讲,也许是。但根据前面的定义,确切地说并不是。我认为最准确地用来解释bar() 对 a 的引用的方法是词法作用域的查找规则,而这些规则只是闭包的一部分, 对于这个例子我们只要稍作调整就可以清晰地展示闭包

640.png

      bar() 显然可以被正常执行。但是在这个例子中,它在自己定义的词法作用域以外的地方执行。**这个函数在定义时的词法作用域以外的地方被调用。闭包使得函数可以继续访问定义时的词法作用域。**我觉得这个才是闭包的真正含义


闭包的真正意义

      从上面的例子我们了解到,什么是闭包?函数在定义时的词法作用域以外的地方被调用。

      因为javascript的作用域规则使用的是词法作用域,函数能访问的作用域永远保持它定义的位置,所以无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。这也实现了闭包真正的意义,通过这种机制,可以在外部作用域中访问不属于这个作用域的内容


循环和闭包

      提到闭包,就不得不提一个典型的例子,那就是循环输出,看下面的例子:

      会输出什么?很简单,5个6,为什么呢?因为延迟函数的回调会在循环结束时才执行。再往更深的原因分析,导致这个结果的原因是什么?是什么导致它不像我们想要的一样,1,2…6地来输出呢

      因为我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个 i 的副本。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。

      如果要解决这个问题应该怎么解决呢,我们真正需要的是一个局部的作用域,每个作用域里对应一个i,对应的问题就可以迎刃而解,很多同学可能第一反应就是利用let和const实现一个块作用域,除了此外还有什么办法呢?对了,还可以使用我们这章说的闭包来实现,利用闭包实现一个自执行函数,将i作为参数传进自执行函数,这样每个自执行函数都形成一块独立的作用域,里面有对应的i,就不会出现之前的情况

640.png


模块

      我们之前提到,闭包可以实现封闭的作用域,并且可以让在这个作用域在别的位置使用,这个功能我们可以利用实现模块和API的封装。比如看下面的例子:

640.png

      不过注意,在模块封装的过程中,需要在外部使用的内容需要用return暴露出来,我们也称这个为模块暴露,对于上面的模块模式,需要具备两个必要条件:

  • 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
  • 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

      如果只有一个,需要把上面的模式改装成单例模式,我们可以用自执行函数来调整,如下:

640.png

      对于单例模式这些,不了解的同学可以看我之前写的有关设计模式的文章,这里就不再做赘述


小结

      当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。如果没能认出闭包,也不了解它的工作原理,在使用它的过程中就很容易犯错,比如在循环中。但同时闭包也是一个非常强大的工具,可以用多种形式来实现模块等模式。

目录
相关文章
|
Web App开发 监控 JavaScript
一些常用的 Vue 性能分析工具
【10月更文挑战第2天】
1121 154
|
11月前
|
监控 JavaScript 前端开发
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
|
消息中间件 Ubuntu Java
在Ubuntu 18.04上安装Apache Kafka的方法
在Ubuntu 18.04上安装Apache Kafka的方法
455 0
|
供应链 监控 数据可视化
物联网技术在物流与供应链管理中的应用与挑战
本文探讨了物联网技术在物流与供应链管理中的应用,通过实时追踪、信息共享、智能化决策等手段,大幅提升了管理效率和智能化水平。特别介绍了板栗看板作为专业可视化工具,在数据监控、分析及协同作业中的重要作用。未来,随着技术的进一步发展,物流与供应链管理将更加智能高效,但也面临数据安全、标准化等挑战。
1141 2
|
安全 Java 程序员
Zig 内存管理
Zig 内存管理
401 1
Guava中Preconditions校验
本文介绍了如何在Java中使用Guava库的Preconditions类进行参数校验,提供了详细的代码示例和常用的校验方法列表。
|
网络安全 开发工具 git
|
JSON 资源调度 JavaScript
Vue框架中Ajax请求的实现方式:使用axios库或fetch API
选择 `axios`还是 `fetch`取决于项目需求和个人偏好。`axios`提供了更丰富的API和更灵活的错误处理方式,适用于需要复杂请求配置的场景。而 `fetch`作为现代浏览器的原生API,使用起来更为简洁,但在旧浏览器兼容性和某些高级特性上可能略显不足。无论选择哪种方式,它们都能有效地在Vue应用中实现Ajax请求的功能。
479 4
|
JavaScript
解决Elementui输入框(text)与文本域(textarea)字体不一样问题
解决Elementui输入框(text)与文本域(textarea)字体不一样问题
1159 5
|
监控 安全 项目管理
如何写一个优质高效的网络项目实施方案?这篇文章值得收藏!
如何写一个优质高效的网络项目实施方案?这篇文章值得收藏!
646 0

热门文章

最新文章