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

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


小结

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

目录
相关文章
|
1月前
|
存储 缓存 JavaScript
哪些情况适合使用块级作用域,哪些情况适合使用函数作用域?
【10月更文挑战第29天】块级作用域和函数作用域在不同的场景下各有优势,合理地选择和运用这两种作用域可以使JavaScript代码更加清晰、高效和易于维护。在实际开发中,需要根据具体的业务需求、代码结构和编程模式来决定使用哪种作用域,或者在适当的情况下结合使用两者,以达到最佳的编程效果。
|
1月前
|
JavaScript 前端开发
块级作用域和函数作用域有什么区别?
【10月更文挑战第29天】块级作用域和函数作用域在JavaScript中各有特点和用途。块级作用域提供了更精细的变量控制,有助于避免变量提升和意外的全局变量污染等问题;而函数作用域则在函数封装和模块化编程等方面有着重要的应用。在实际开发中,需要根据具体的需求和场景合理地选择使用哪种作用域来声明变量和组织代码。
|
2月前
|
JavaScript 前端开发
作用域和作用域链及预解析
作用域和作用域链及预解析
25 4
|
3月前
C 作用域详解
在 C 语言中,作用域决定了变量和函数的可见性和生命周期,包括块作用域、函数作用域、文件作用域和全局作用域。块作用域内的变量仅在块内有效,函数作用域内的变量在整个函数内有效,文件作用域内的全局变量和函数在整个文件内有效,而全局作用域内的变量和函数在整个程序运行期间有效。作用域的优先级遵循局部变量优先的原则,局部变量会遮蔽同名的全局变量。变量的生命周期分为局部变量(函数调用时创建和销毁)、全局变量(程序开始时创建和结束时销毁)以及静态变量(整个程序期间有效)。理解作用域有助于避免命名冲突和错误,提高代码的可读性和可维护性。
|
7月前
|
JavaScript 前端开发 Python
函数与作用域
编程中的函数与作用域概念。函数是可重用的代码块,能提高代码的可读性、可维护性和复用性。基础用法包括定义、调用和返回值。高级用法涉及函数嵌套、匿名函数(lambda函数)和装饰器。装饰器能在不修改原函数代码的情况下添加功能。 作用域决定了变量的可见范围,从内到外是局部、嵌套、全局和内置作用域。闭包是能访问外部函数变量的内部函数,即使外部函数执行完毕,闭包仍能保留其状态。闭包常用于实现特殊功能,如记忆化和延迟执行。 立即执行函数表达式(IIFE)是JavaScript中的模式,用于创建私有作用域和防止变量污染全局。IIFE常用于封装变量、避免命名冲突以及实现模块化和函数作为参数传递。
|
7月前
|
自然语言处理 JavaScript 前端开发
深入理解作用域、作用域链和闭包
在 JavaScript 中,作用域是指变量在代码中可访问的范围。理解 JavaScript 的作用域和作用域链对于编写高质量的代码至关重要。本文将详细介绍 JavaScript 中的词法作用域、作用域链和闭包的概念,并探讨它们在实际开发中的应用场景。
|
Linux 网络架构
暂时性死区以及函数作用域
暂时性死区以及函数作用域
182 0
|
存储 缓存 JavaScript
深入理解作用域和闭包(下)
深入理解作用域和闭包(下)
深入理解作用域和闭包(下)
|
存储 JavaScript 前端开发
深入理解作用域和闭包(上)
深入理解作用域和闭包(上)
深入理解作用域和闭包(上)
|
自然语言处理 前端开发 JavaScript
作用域闭包
作用域闭包
91 0
下一篇
DataWorks