一篇文章帮你真正理解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

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


小结

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

目录
相关文章
|
监控 JavaScript 前端开发
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
|
XML 前端开发 安全
如何使用 CSS 中的 :root 伪类选择器
如何使用 CSS 中的 :root 伪类选择器
625 0
|
JavaScript
Vue进阶篇(二)-彻底改变 Vue.js 状态管理:深入探讨Vue 3 Setup中的DefineModel
Vue进阶篇(二)-彻底改变 Vue.js 状态管理:深入探讨Vue 3 Setup中的DefineModel
|
前端开发 JavaScript
来认识一下 styleSheets,好像有点高级的样子
使用 js 操作样式我们通常操作的是 dom 的 style 属性,也就是我们所说的行内样式,那么有没有一种方法可以操作 css 文件中的样式呢?答案是肯定的,我们可以使用 cssom 来操作 css
459 0
|
JavaScript
Vue组件入门(九)v-model 自定义修饰符
Vue组件入门(九)v-model 自定义修饰符
人工智能 缓存 前端开发
6284 19
人工智能 JavaScript 开发工具
3252 4
缓存 JavaScript Shell
1551 1
开发工具 Swift git
1031 1

热门文章

最新文章