前言
在说一个概念前,咱们须要确定它的前提,此文以 ECMAScript5 为根底撰写
一句话解释
执行上下文就是一段代码执行时所带的所有信息
执行上下文是什么
《重学前端》的作者 winter 已经对什么是执行上下文做过这样的解释:
JavaScript 规范把一段代码(包含函数),执行所需的所有信息定义为:“执行上下文
并且他整顿出在不同 ECMAScript 版本中执行上下文所代表的含意:
执行上下文在 ES3 中,蕴含三个局部。
- scope:作用域,也经常被叫做作用域链
- variable object:变量对象,用来存储变量的对象
- this value:this 值
在 ES5 中,咱们改良了命名形式,把执行上下文最后的三个局部改成上面这个样子
- lexical environment:词法环境,当获取变量时应用
- variable environment:变量环境,当申明变量时应用
- this value:this 值
在 ES2018 中,执行上下文又变成了这个样子,this 值被纳入 lexical environment,然而减少了不少内容
- lexical environment:词法环境,当获取变量或者 this 值时应用
- variable environment:变量环境,当申明变量时应用
- code evaluation state: 用于复原代码执行地位
- Function:执行的工作是函数时应用,示意正在被执行的函数
- ScriptOrModule:执行的工作是脚本或者模块时应用,示意正在被执行的代码
- Realm:应用的根底库和内置对象实力
- Generator:仅生成器上下文有这个属性,示意以后生成器
总结的很残缺,依照 选新不选旧
准则,本文应该以 ES2022 为切入点开展,最次也要 ES2018,但支流的解释执行上下文都以 ES3/ES5 为例,衡量之后,笔者将以 ES5 为根底撰写执行上下文,并在后续补充阐明 ES3 中的执行上下文
执行生命周期
咱们在讲 词法环境 时,已经画过一张执行生命周期图,过后所讲的词法环境是在 (预)编译阶段 产生,当初讲的执行上下文是在引擎 执行阶段 进行
一段代码如果要执行,首先会往调用栈(call stack)中压入全局执行上下文;再创立词法环境,此时变量该晋升晋升,函数该晋升晋升,并将这些变量注销到词法环境中(编译阶段);接着进入执行阶段,执行可执行代码,该赋值赋值,遇到函数,就创立一个函数执行上下文,并往调用栈中压入该函数的执行上下文;而后创立该函数的词法环境,当该函数执行完后,从调用栈中弹出;重复循环,到最初调用栈中只剩一个全局执行上下文,除非你敞开浏览器,不然全局执行上下文不会弹出
咱们要往调用栈中压入执行上下文,调用栈的数据结构为 栈
。特点为先进后出
如果咱们的代码是这样的
var a = 1;
function foo() {function bar() {console.log(a);
}
bar();}
function baz() {foo();
}
baz();
那么执行过程应该是这样:
图中的蓝色方块为 执行上下文
,里面黑框白底的区域就是模仿 调用栈
。整个过程遵循先进后出的准则
- 在任何代码执行之前,先创立全局执行上下文,并往调用栈中压栈(编译阶段)
- 创立词法环境,注销函数申明和变量申明(编译阶段)
- 引擎执行到
baz()
,创立baz()
的函数执行上下文,并往调用栈中压栈 - 函数
baz()
调用foo()
,创立foo()
执行上下文,并将其压入调用栈中, - 函数
foo()
调用bar()
,创立bar()
执行上下文,并将其压入调用栈, - 函数
bar()
执行console.log()
,同理将其压入调用栈, - 执行完
console.log()
后,被弹出 - 函数
bar()
执行结束,弹出调用栈 - 函数
foo()
也执行结束,弹出调用栈 - 函数
baz()
同样执行结束,弹出调用栈
只剩下全局执行上下文,留在栈底
在这里,咱们看到 console.log()
也被压入到执行栈中,不禁有个思考,哪些代码元素会被执行到调用栈中呢?
可执行代码
事实上,不仅仅是 function 能够作为执行上下文在执行栈中运行,在 JavaScript 里定义了四种可执行代码:
- global code:整个 js 文件
- function code:函数代码
- module:模块代码
- eval code:放在 eval 的代码
所以才会看到 console.log()
被压入调用栈中,因为它属于 global code
执行步骤
JavaScript 引擎是依照可执行代码来执行代码的,每次执行步骤如下:
- 创立一个新的执行上下文(Execution Context)
- 创立一个新的词法环境(Lexical Environment)
- 把 LexicalEnvironment 和 VariableEnvironment 指向新创建的词法环境
- 把这个执行上下文压入执行栈并成为正在运行的执行上下文
- 执行代码
- 执行完结后,把这个执行上下文弹出执行栈
如何创立执行上下文
到当初,咱们曾经晓得 JavaScript 是如何治理执行上下文的,当初让咱们理解一下 JavaScript 引擎是怎么创立执行上下文的
创立执行上下文有两个阶段:1) 创立阶段 和 2) 执行阶段
创立阶段
在 JavaScript 代码执行前,执行上下文将经验创立阶段。在创立阶段会产生以下三件事:
- this 值的确定,即咱们所熟知的 this 绑定。
- 创立 词法环境组件(LexicalEnvironment component)
- 创立 变量环境组件(VariableEnvironment component)
所以执行上下文在概念上示意如下:
ExecutionContext = {
ThisBinding = <this value>,
LexicalEnvironment = {...},
VariableEnvironment = {...},
}
这里须要再多嘴一句:
在很多文章中咱们看到执行上下文只有 LexicalEnvironment 和 VariableEnvironment,并没有 this。那是因为在 ES2018 后,this 就归纳到 LexicalEnvironment(如上文 winter 所说),但本文是以 ES5 为根底撰写,故此版本的执行上下文中是有 this 的
this 绑定
this 的指向很简略,谁调用我,我只想谁
在执行上下文中,this 就指向那个调用者
词法环境组件 和 变量环境组件
这两”姐妹“有点像,只是分工不同。
变量环境组件(VariableEnvironment component) 用来注销 var
、function
等变量申明
词法环境组件(LexicalEnvironment component) 用来注销 let
、const
、class
等变量申明
按上例能够这么画图:
LexicalEnvironment
和 VariableEnvironment
则都是词法环境(Lexical Environment)。很多文章中常把 LexicalEnvironment
了解成 词法环境,这是不对的,LexicalEnvironment
是一个单词,示意执行上下文中的是标识 let
、const
、class
等变量申明,而 VariableEnvironment
则是标识 var
、function
等变量申明
如果非要用中文来示意 LexicalEnvironment
的话,我更违心用 词法环境组件 来示意;同理,VariableEnvironment
则用 变量环境组件 来示意
对我而言,变量环境组件和词法环境组件就好比”装黄豆瓶“和”装绿豆瓶“,一个负责装黄豆(var,function),一个负责装绿豆(let,const,class),它们指向词法环境,实质是从词法环境中拿数据
所以无论是词法环境组件还是变量环境组件,都有一个环境记录器和一个 outer 对象,其中环境记录器记录变量,outer 指向父级作用域
具体能够看 ECMAScript 6 规范中的第八节 具体理解一下
回头看上述例子:
bar()
函数中的 console.log(a)
,本环境记录器中找不到,就引着 outer
找,在 window 中找到变量 a,赋值后,弹出,foo()
执行完后也弹出,baz()
执行完弹出,留下全局执行上下文在栈底
之前在说 词法环境 时,咱们曾下过这样的定义:outer 就是指向词法环境的父级词法环境(作用域)
然而这里就有个纳闷了,outer 既然指词法环境的父级作用域,那作用域链从那里来?
以下面的 demo 为例,bar 的执行上下文的伪代码:
BarExecutionContext = {
ThisBinding = <Global Object>,
LexicalEnvironment = {EnvironmentRecord: { ...},
outer: <FooLexicalEnvironment>
},
VariableEnvironment = {EnvironmentRecord: { ...},
outer: <FooLexicalEnvironment>
},
}
bar 的 outer 指向 foo 执行上下文,foo 的 outer 指向 window,变量先从以后执行作用域查找变量,如果找不到,就引着 outer 持续查找。这一过程中,bar 作用域—foo 作用域—window 作用域。
当代码要拜访一个变量时——首先会搜寻以后词法环境,而后搜寻外部环境,而后搜寻更内部的环境,以此类推,直到全局词法环境
而咱们潜意识中的”作用域链“是在 ES3 中的才有的,因为那个时候的执行上下文中有 scope(作用域),以后作用域找不到变量,就往外层作用域中找,而后再到更外层的作用域找,晓得全局作用域,作用域与作用域之间以 链表 的模式连贯着,这就是作用域链
执行上下文有几种
分三种
- 全局执行上下文
- 函数执行上下文
- eval 执行上下文
ECMAScript3 中的执行上下文
如果你喜爱看无关执行上下文的文章,应该常看到这样的形容,执行上下文由变量对象(Variable object,VO)、作用域链(Scope chain)、this 形成。
其实咱们能够把变量对象(VO)看成是 ES5 中的词法环境,scope 为词法环境中的 outer
如何追踪执行上下文栈
例如:
function foo1() {foo2();
}
function foo2() {foo3();
}
function foo3() {foo4();
}
function foo4() {console.lg('foo4');
}
foo1();
失去谬误提醒如图:
或者在 Chrome 中执行代码,打断点失去:
对于变量对象与流动对象
笔者最开始理解执行上下文、执行上下文栈以及闭包时,大家是用变量对象和流动对象来解释的,这是 ES3 时的词汇,而本文是以 ES5 为规范开展
其两者的涵义简略来说,变量对象(Variable object)是与执行上下文相干的对象,存储了在上下文中定义的变量和函数申明。而在函数上下文中,咱们用流动对象(activation object,AO)来示意变量对象
作用域在(预)编译阶段确定,然而作用域链是在执行上下文的创立阶段实现生产的。因为函数在调用时,才会开始创立对应的执行上下文。执行上下文包含了:变量对象、作用域链以及 this 的指向
总结
执行上下文能够了解为函数的执行环境,当函数执行时,都会创立一个执行环境
每次只能有一个执行上下文处于运行状态,因为 JavaScript 是单线程语言,它由执行栈或(叫)调用栈来治理
创立一个函数,就生成了一个作用域;调用一个函数,就生成一个作用域链
执行上下文创立阶段分为绑定 this,创立词法环境,变量环境三步
调用函数时,创立一个新的词法环境
词法环境这个说法,是 ES5 标准中的内容,能够了解为 ES3 中的变量对象,scope 为 词法环境中的 outer
在 ES5 中,词法环境 和 变量环境 的一个不同就是前者被用来存储函数申明和变量(let
和 const
)绑定,而后者只用来存储 var
变量绑定
在 ES3 时,执行上下文包含了变量对象、作用域链以及 this
在 ES5 时,执行上下文则包含词法环境、变量环境、this。其中词法环境或者变量环境都是由用于环境记录器和 outer 对象组成,其中 outer 指向父级作用域,环境记录器记录自在变量
Q&A
Q:是不是说在定义时确认了作用域,在调用时确认了作用域链?
A:yes,这里须要留神的是每一个执行上下文都会进行晋升操作
Q:ES5 中的执行上下文的词法环境和编译阶段的词法环境有什么不同?
A:一段 JavaScript 代码在执行之前须要被 JavaScript 引擎编译,编译实现之后,才会进行执行阶段,大抵流程如下:
参考资料
- 了解 JavaScript 中的执行上下文和执行栈
- Understanding Execution Context and Execution Stack in JavaScript
- 官网 ES
- JavaScript 执行上下文 · 变量对象
- 老司机也会在闭包相干知识点翻车(上)
- 一篇文章看懂 JS 执行上下文
系列文章
- 深刻了解 JavaScript——开篇
- 深刻了解 JavaScript——JavaScript 是什么
- 深刻了解 JavaScript——JavaScript 由什么组成
- 深刻了解 JavaScript——所有皆对象
- 深刻了解 JavaScript——Object(对象)
- 深刻了解 JavaScript——new 做了什么
- 深刻了解 JavaScript——Object.create
- 深刻了解 JavaScript——拷贝的机密
- 深刻了解 JavaScript——原型
- 深刻了解 JavaScript——继承
- 深刻了解 JavaScript——JavaScript 中的始皇
- 深刻了解 JavaScript——instanceof——找祖籍
- 深刻了解 JavaScript——Function
- 深刻了解 JavaScript——作用域
- 深刻了解 JavaScript——this 关键字
- 深刻了解 JavaScript——call、apply、bind 三大将
- 深刻了解 JavaScript——立刻执行函数(IIFE)
- 深刻了解 JavaScript——词法环境