详解JavaScript中的作用域链与闭包

作者:大眼睛图图 时间:2024-04-22 13:07:00 

作用域链

首先来看看这段代码:

var a = '喜羊羊';
function A(){
   console.log(a);
   a = '美羊羊';
   function B(){
       console.log(a);
   }
   B();
}
A();

在这里毫无疑问结果肯定是我们想到的先打印喜羊羊,再打印美羊羊。因为作用域链嘛,如果当前层没找到,那么就去当前层的上一级找。

那么再看这道


function bar() {
   console.log(myName)
}
function foo() {
   var myName = "极客邦"
   bar()
}
var myName = "极客时间"
foo()

是不是感觉是打印极客邦?如果是的话,那么恭喜你,掉坑里了。(还不赶快爬起来,补一补作用域链的知识)。

为什么打印不是极客邦而是极客时间呢?

既然问题出现在了对作用域链的理解上,那么就再回到作用域链的定义上吧。

其实在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。

比如上面那段代码在查找 myName 变量时,如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找

为了直观理解,你可以看下面这张图:

详解JavaScript中的作用域链与闭包

看到这张图我猜你又纳闷了,为什么bar函数创建的执行上下文中的outer会指向全局??

哈哈哈,这里就要涉及到了词法作用域了

词法作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。

这么讲可能不太好理解,你可以看下面这张图:

详解JavaScript中的作用域链与闭包

从图中可以看出,词法作用域就是根据代码的位置来决定的,其中 main 函数包含了 bar 函数,bar 函数中包含了 foo 函数,因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:foo 函数作用域—>bar 函数作用域—>main 函数作用域—> 全局作用域。

明白了词法作用域,那么我们再回到刚刚的问题。

为什么bar函数创建的执行上下文中的outer会指向全局

这是因为根据词法作用域,而词法作用域又是根据代码的位置,而bar函数代码的位置就是包裹在全局下,而喜羊羊那个例子中的B函数是在A函数的环境下,所以会造成它们的词法作用域链不同,也就导致函数作用域链不同了。

所以我们才有那句话词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。

也就是只和代码位置有关,和函数直接如何调用没关系

闭包

老生常谈的问题,这次再从一个更深入的角度来理解一下。

看下面这段代码:


function foo() {
   var myName = "极客时间"
   let test1 = 1
   const test2 = 2
   var innerBar = {
       getName:function(){
           console.log(test1)
           return myName
       },
       setName:function(newName){
           myName = newName
       }
   }
   return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())

这段代码乍一看没有什么问题,但是这里有一个细节很多人会忽视。

在foo()执行完将返回值给bar时,这里foo函数会从调用栈中弹出,变量都会被回收。既然变量都被回收了,那么bar.setName()这些调用方法从何而来??

foo执行完后的情况可以参考下图:

详解JavaScript中的作用域链与闭包

从上图可以看出,foo 函数执行完成之后,其执行上下文从栈顶弹出了,但是由于返回的 setNamegetName 方法中使用了 foo 函数内部的变量 myNametest1,所以这两个变量依然保存在内存中。这像极了 setNamegetName 方法背的一个专属背包,无论在哪里调用了 setName getName 方法,它们都会背着这个foo函数的专属背包。

之所以是专属背包,是因为除了 setNamegetName 函数之外,其他任何地方都是无法访问该背包的,我们就可以把这个背包称为 foo 函数的闭包。

来源:https://juejin.cn/post/7163893183804342308

标签:JavaScript,作用域链,闭包
0
投稿

猜你喜欢

  • Go语言如何使用golang-jwt/jwt/v4进行JWT鉴权详解

    2024-02-07 05:13:41
  • Python Socket编程详细介绍

    2021-01-02 02:56:21
  • 深入理解r2dbc在mysql中的使用

    2024-01-26 20:14:10
  • js金额浮点格式化控件

    2008-08-01 16:52:00
  • SQL Join的一些总结(实例)

    2012-08-21 10:19:29
  • Python去除列表中重复元素的方法

    2021-06-23 20:44:27
  • 谈谈为什么你的 JavaScript 代码如此冗长

    2024-04-17 09:50:07
  • 桌面中心(二)数据库写入

    2023-11-18 12:26:15
  • 一文吃透Go的内置RPC原理

    2024-02-03 08:45:53
  • VS Code配置Go语言开发环境的详细教程

    2024-05-11 09:08:31
  • PHP VBS JS 函数 对照表

    2024-04-29 13:55:57
  • python函数不定长参数使用方法解析

    2022-07-05 23:47:20
  • TensorFlow:将ckpt文件固化成pb文件教程

    2021-01-24 11:33:45
  • Python socket连接中的粘包、精确传输问题实例分析

    2023-12-21 23:42:48
  • 基于Python的文件类型和字符串详解

    2023-08-03 12:51:26
  • python中for循环把字符串或者字典添加到列表的方法

    2022-05-12 19:16:44
  • Matplotlib绘制子图的常见几种方法

    2022-04-04 06:19:47
  • Python爬虫beautifulsoup4常用的解析方法总结

    2022-09-01 11:58:54
  • python实现发送带附件的邮件代码分享

    2021-11-24 12:28:30
  • 去除python中的字符串空格的简单方法

    2022-11-01 10:33:22
  • asp之家 网络编程 m.aspxhome.com