You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
person被调用时,在person的执行上下文会创建一个活动对象AO,并且被初始化为 AO = [arguments]。
随后AO又被当做变量对象VO(没错就是EC创建阶段的变量对象variable object)进行变量初始化,此时 VO = [arguments].concat([name,age,gender,say])
The global execution context gets some slightly different handling as it does not have arguments so it does not need a defined Activation object to refer to them. [...] The global object is used as the Variable object, which is why globally declared functions become properties of the global object.
https://wolfdu.fun/post?postId=5a26838fc7ad1346411b7265
我们从一道引发过很多人思考🤔的面试题开始
这两段代码的返回结果是相同的都会返回“local scope”,那么他们的不同之处在哪里呢?
从代码的行文来看我们会发现:
checkscope()
返回的是内部函数f()
的执行结果checkscope()
返回的是内部函数f
,然后再执行返回的函数so他们在执行过程中到底由什么区别?
我们都知道在JavaScript引擎在执行一段代码前会做很多事情例如:变量的声明,提升,this的绑定等等。
用ECMAScript规范-10.3 执行环境来描述就是
那么执行什么样的代码段控制器才会进入执行环境呢或者我们常说的执行上下文(Execution Context)?
首先引入一个概念👇
可执行代码类型
一共由3种可执行代码。
*全局代码(Global code):*脚本“程序”处理的源代码文本,也就是代码首次执行的默认环境
*函数代码(Function code ):*一个函数的body,不包括内部嵌套函数的body
*Eval代码(Eval code ):*在eval函数中执行的文本
所以在执行例子中代码的时候首先进入的是全局执行上下文(global context)
(⊙o⊙)…执行上下文(这里只是引入概念,后面有详细解释)是神马👇
talking is cheap😂
我们可以发现图中这么多执行上下文(global Context,Execution Context),这些执行上下文是如何管理的呢?
执行上下文栈(ECS Execution Context Stack)
所以 JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文。当前活动的多个执行上下文(EC)在逻辑上形成一个栈结构。该逻辑栈的最顶层的执行环境称为当前运行的执行环境。
那么接下来我们从执行上下文栈的角度来分析分析A代码和B代码。
我们使用空数组来模拟ECS:
当JavaScript遇到A代码段时:
checkscope()
时会创建函数的执行上下文checkscopeContext,并将其压入执行上下文栈中:f()
会创建函数执行上下问fContext,并将其压入执行上下文栈:f
执行完毕,执行上下文从栈中弹出checkscope
执行完毕,执行上下文从栈中弹出机智的你是不是已经发现了A代码段和B代码段的区别了呢?🤓
这里我们简单用数组来描述B代码段的执行过程:
至此我们知道了A代码与B代码执行上下文出入栈的区别,但是在代码执行前的更多细节我们无从得知,其中又有什么区别呢?
例如在创建执行上下文时做了什么?
那么接下来分析分析执行上下文(EC)多么平(生)滑(硬)的转折^_^
执行上下文(EC Execution Context)
这里我们知道每当函数被执行前,就会创建一个执行上下文。
在JavaScript解析器中每个执行上下文都有两个阶段:
这里我们可以将EC抽象为一个对象来表示:
这里我们先来聊聊创建阶段的创建变量对象(VO Variable Object)因为建立作用域链会涉及到变量对象相关内容,所以这里了解VO的创建阶段
创建变量对象(VO)
以下是创建变量过程:
我们通过A代码段中执行
checkscope
函数时创建EC为例,来分析EC创建阶段中变量对象的创建过程:这里主要关注执行上下文中的VO
代码的执行阶段
在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值
这里需要引入一个概念:活动对象(activation object)
举个栗子:
person
被调用时,在person
的执行上下文会创建一个活动对象AO,并且被初始化为 AO = [arguments]。随后AO又被当做变量对象VO(没错就是EC创建阶段的变量对象variable object)进行变量初始化,此时 VO = [arguments].concat([name,age,gender,say])
所以VO和AO的关系我们可以这么理解:
所以我们接着创建变量对象中的栗子继续执行代码就是:
接下来,继续执行
checkscope
执行流,执行return f()
,执行f
函数。万事俱备,我们了解了VO的创建和代码执行阶段,接下来看一看作用链的建立。
建立作用域链
作用域链,它在解释器进入到一个执行上下文时初始化完成并将其分配给当前执行上下文。每个执行上下文的作用域链由当前上下文的变量对象(VO)及父级执行上下文的作用域链构成。
可以理解为:
currentScopeChain = currentVO + all parents scopes
废话少说,刚正面吧!!!
这里分析A代码段中每个EC中的的作用域链是怎么建立起来(包含其他执行过程):
checkscope
函数被创建,初始化内部属性[[scope]]
。checkscope
函数代码,将函数EC压如ECS中checkscope
函数执行上下文,它会复制checkscope
函数的[[Scope]]属性并构建作用域checkscope
函数EC的VOcheckscope
函数EC开始代码执行阶段(VO-->AO)checkscope
函数EC的作用域链前端f
被创建,初始化内部属性[[scope]]
return f()
接下来就是执行一个函数代码的完整流程:
f
函数代码,将函数EC压如ECS中f
函数执行上下文,它会复制f
函数的[[Scope]]属性并构建作用域f
函数EC的VOf
EC开始代码执行阶段(VO-->AO)f
函数 EC的作用域链前端f
执行完毕,函数EC从ECS中弹出同时返回
scope
, 解释器根据fContextObj.scopeChain
查找变量scope
,在checkscopeContextObj.scopeChain
中找到scope: 'local scope'
。checkscope
函数执行完毕,函数EC从ECS中弹出到这里,借助作用域链的建立,我们就分析完了A代码段的整个执行过程。咻!!!💀💀💀
那么接下来我们来看看B代码段(⊙o⊙)…👻👻👻
其实这两段代码区别,我们早就说过了,就是执行上下文的栈的变化存在不同点,其他的处理过程基本都是一样的😤
那为什么还要进行执行上下文的分析呢?因为我不知道里面到底做了什么,万一有很多小秘密呢😱😱😱
总结
通过深入系列的文章加上一些拓展的阅读,基本上可以让我理清楚,JavaScript解析器在执行代码的前前后后的一系列事情。也深度的解释了一些问题,变量、函数声明的提升,作用域链的建立等等。
然而。。。
是不是突然发现搞懂了这些你依然还是写不出牛逼闪闪的代码😱😱😱😱
阅读文章:
JavaScript深入之执行上下文栈
JavaScript深入之变量对象
JavaScript深入之作用域链
JavaScript深入之执行上下文
一道js面试题引发的思考
What is the Execution Context & Stack in JavaScript?
若文中有知识整理错误或遗漏的地方请务必指出,非常感谢。如果对你有一丢丢帮助或引起你的思考,可以点赞鼓励一下作者=^_^=
The text was updated successfully, but these errors were encountered: