细说JS中执行上下文与作用域链

前言

相信很多小伙伴在初学JavaScript时会对执行上下文,作用域链等概念混淆,我将通过这篇文章用代码和图文的形式来给大家分享一下理解JS中执行上下文与作用域链的方法。话不多说,直接开整。

image.png

执行上下文

在JavaScript引擎执行代码之前,会创建一个执行上下文,用来存储变量对象VO(Variable Object)定义在全局执行上下文(globalEC)中,存储全局变量函数

活动对象AO(Activation Object)定义在函数执行上下文(fnEC)中(准确来说,在函数开始执行前才创建),存储局部变量子函数以及arguments、以及作用域链(Scope Chain)。同时,还会创建一个执行上下文栈,用来管理执行上下文的入栈和出栈操作。

可能有些小伙伴对栈这种结构比较陌生,下面我将为大家简要介绍一下栈的基本特性:

-栈结构 : 特殊的数组,先进后出

-调用栈 : js引擎用来追踪函数调用关系的

-栈溢出 : 栈结构有一定的内存限制,当超出了这个最大的内存则会爆栈,导致栈溢出

直接上代码:

function bar(){
    console.log(myName);
}

function foo(){
    var myName = "Tom";
    bar();
}
var myName = "Jerry";
foo();

以谷歌浏览器为例,谷歌V8引擎在执行这段代码时,会创建一个调用栈,然后将全局执行上下文压入栈底,当某个函数被调用时则又将该函数压入到调用栈中,直到该该函数彻底执行完毕才将该函数在栈中占用的内存销毁。是不是感觉很绕,看下图,大家很快就能get到我说的点了。

image.png

上图便是js引擎在编译该代码时,所会产生的栈结构。是不是还是有点迷惑,下面我将用简短的语言来为大家梳理一下该调用栈的产生过程。

  1. js引擎一看到这段代码时会先进行全局预编译创建一个全局执行上下文对象,此时从上往下开始编译,记录bar(),foo(),myName在全局执行上下文中(VO)。
  2. 接下来执行foo()函数,此时会创建一个AO对象也即foo的执行上下文入栈,再将foo()函数中的变量放入变量环境中
  3. 在foo()的执行上下文中又执行了bar()函数,这时又将创建一个Ao对象即bar的执行上下文入栈

至此调用栈便创建完毕,接下来我将在此基础上带大家彻底掌握作用域链,大家此时请把这张图刻在脑子里。

作用域链

在正式介绍作用域链之前,大家先来看官方对作用域链的定义:

作用域链(Scope Chain)是在 JavaScript 中用来解析标识符(变量名)的一种机制。它是一个由多个执行上下文的变量对象(Variable Object)组成的链表,用于确定当前代码中标识符的访问权限和可见性。当查找变量时,JavaScript 引擎会沿着作用域链从内到外逐级搜索,直到找到匹配的标识符或者搜索到最外层的全局执行上下文为止。

在每个执行上下文中,都有一个对应的变量对象,其中包含了该上下文中声明的所有变量、函数声明以及形参。当需要访问一个变量时,JavaScript 引擎会先在当前执行上下文的变量对象中查找,如果找不到,则会沿着作用域链向上一级的执行上下文中查找,直到找到匹配的标识符或者搜索到全局执行上下文为止。

作用域链的形成是在函数定义时确定的,而不是在函数调用时。因此,它与函数的词法作用域(Lexical Scope)密切相关,也就是说,作用域链的结构取决于函数在代码中的位置,而不是函数被调用的位置。

大家看完是什么感受呢?是不是一头雾水不知所云,哈哈哈😀,我第一次看的时候也看不懂😂,不过没关系,接下来我将用图文的形式带大家彻底理解作用域链。

image.png

话不多说,先上代码!

function bar(){
    console.log(myName);
}

function foo(){
    var myName = "Tom";
    bar();
}
var myName = "Jerry";
foo();

大家认为这段代码最后会输出什么呢?Tom还是Jerry

image.png
大家有没有写对呢?思考,为什么这段代码输出的是Jerry而不是Tom呢?

让我们回到上一个知识点-执行上下文。在js引擎中每一个执行上下文中都会内置一个outer指针来指向该函数出现的地方,正是因为有了outer指针的指向才形成了作用域链。是不是又有点头晕,没关系,我们上图。

image.png

解释:

当执行bar()函数时,直选到语句console.log(myName);,此时在bar函数中查找myName变量发现bar中并不存在,然后顺着outer指针指向在全局作用域中找到了myName变量,所以最后输出Jerry

总结:outer指针的指向为该函数第一次出现在的地方即,该函数第一次出现在哪个执行上下文中,该函数的outer指针就指向哪。

本篇文章就到此为止啦,希望通过这篇文章能对你理解作用域链和执行上下文有所帮助,本人水平有限难免会有纰漏,欢迎大家指正。如觉得这篇文章对你有帮助的话,欢迎点赞收藏加关注,感谢支持🌹🌹。

image.png
([本人掘金地址](https://juejin.cn/post/7366072920617336884))欢迎大家关注支持

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小A远离BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值