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
举个例子:var a = 2,在上下文创建阶段扫描代码,变量提升'var a',可以理解为此时执行的是var a (实际并没有执行而只是扫描)。而在执行阶段等于引擎就执行的是a=2了。但是注意一点,a=2这个操作,实际是 作用域(规则)配合引擎进行的LHS查询,为VO中的a属性赋值2。那我们之前不是刚说过VO中的属性是不能访问的吗?这里怎么可以访问了?其实我们之前说的没错,只是到了执行阶段VO产生一些变化了:
JavaScript compilation doesn't happen in a build step ahead of time, as with other languages. // JS的编译过程不是发生在构建之前的。
JS engines use all kinds of tricks (like JITs, which lazy compile and even hot re-compile, etc.) which are well beyond the "scope" of our discussion here. // js引擎会用尽各种方法(比如JIT,可以延迟编译甚至实施重编译)来保证性能最佳。
that any snippet of JavaScript has to be compiled before (usually right before!) it's executed. // 任何js代码片段在执行前都要进行编译(通常就在执行前)。
此文结构是ES5规范前和后的执行上下文相关知识分开介绍。如果对ES5之前的比较熟悉了,可以跳过。想复习一下或不熟悉的也可以看一看。(看了网上很多相关记录的, 其实我觉得基本都众说纷纭,很多种说法,而且有些知识点已经不是以前书中所记录的样子了,包括某些著名书籍如第三版高程/y-d-k-js。当然我这仅仅只是站在chrome浏览器的角度上说的这话,我的理解以chrome的调试为准。)在参考过不少国内外资料也包括部分规范后,我决定写几篇文章记录下,于我而言算为之后秋招复习,更想知道各位对这篇文章有不同的有“证据”,符合逻辑的分析、看法。
ES5规范前
执行上下文是什么?
执行上下文的定义是: 当前js代码被解析和执行时所处环境的抽象概念。
可以理解为,当前js代码的运行环境。
再来了解下js的运行环境,有三种:
每进入一个不同的运行环境就会创建一个相应的执行上下文(Execution Context),容易知道一个js程序中一般会创建多个执行上下文,并且js引擎会以栈的方式对这些执行上下文进行处理,形成函数调用栈(call stack)。
一张图理解函数调用栈(call stack)
简单分析下,这个图里例子的函数调用栈的出栈入栈情况。
再来个例子测试下理解了没?
思考一下再看答案:
执行上下文的生命周期
知道了执行上下文和函数调用栈是啥,结合起来是怎么利用的。接下来,就来分析下执行上下文的生命周期。
执行上下文的生命周期有两个阶段:
创建阶段
此阶段主要做了三件事情:
代码执行阶段
此阶段会完成变量赋值,函数引用,以及执行其他代码。
用张完整的图来解释执行上下文的生命周期就是:
了解了执行上下文的生命周期后,接下来我们从创建阶段开始一步步说。
创建阶段
我们在介绍生命周期那里解释了创建阶段会做的三件事情,现在从创建变量对象开始说起。
创建变量对象
一、对于函数执行上下文而言
创建变量对象(Variable Object),前辈们基本都是总结出了三个过程:
面试里所谓的变量提升,其实创建变量对象的三个规则里已经说明了。
二、对于全局执行上下文而言,
以浏览器为例,全局对象为window。
全局上下文有一个特殊的地方,它的变量对象,就是window,所有的变量和函数都是window对象的属性方法。而这个特殊,在this指向上也同样适用,this也是指向window。
利用以上规则,我们举个例子来分析创建阶段的执行上下文EC:
看懂了吗? 举个例子再试试?会的可以跳过。
创建阶段的作用域链和this绑定暂时跳过,稍后介绍。
执行阶段
我们已经知道代码执行阶段会完成变量赋值,函数引用,以及执行其他代码。
说白了就是引擎开始执行代码了嘛。
举个例子:
var a = 2
,在上下文创建阶段扫描代码,变量提升'var a',可以理解为此时执行的是var a
(实际并没有执行而只是扫描)。而在执行阶段等于引擎就执行的是a=2
了。但是注意一点,a=2
这个操作,实际是 作用域(规则)配合引擎进行的LHS查询,为VO中的a属性赋值2。那我们之前不是刚说过VO中的属性是不能访问的吗?这里怎么可以访问了?其实我们之前说的没错,只是到了执行阶段VO产生一些变化了:进入执行阶段之后,变量对象(Variable Object)转变为了活动对象(Activi Object),里面的属性都能被访问了,然后开始进行执行阶段的操作。
这也就是我们思维导图里画的所谓的 VO => AO。
还是上面那个例子,给观众姥爷们感受一下执行阶段产生的影响(下面的例子也会把作用域链的值也写出来,用数组表示,不懂的可先感受下,暂时跳过):
贴个图验证下:
局部变量指的是当前函数的局部变量。在函数执行上下文中,当前函数的局部变量、函数、形参都是声明保存在变量对象中的。
再来个例子,试着写一写?
作用域链
我们知道函数在调用激活时,会开始创建对应的执行上下文,在执行上下文生成的过程中,变量对象,作用域链,以及this的值会分别被确定。这里我们来说下作用域链:
作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
结合一个例子来理解:
在上面的例子中,当执行到刚刚调用innerTest函数,进入innerTest函数环境,此时处于innerTest执行上下文的创建阶段。全局执行上下文和test函数执行上下文已进入执行阶段,所以他们的活动对象和变量对象分别是AO(global),AO(test)和VO(innerTest),而innerTest的作用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,如下:
我们这里直接使用数组表示作用域链,作用域链的活动对象或变量对象可以直接理解为作用域(但实际上作用域只是一套规则)。
作用域链的第一项永远是当前作用域(当前上下文的变量对象或活动对象);
最后一项永远是全局作用域(全局执行上下文的活动对象);
作用域链保证了变量和函数的有序访问,查找方式是沿着作用域链从左至右查找变量或函数,找到则会停止查找,找不到则一直查找到全局作用域,再找不到则会抛出引用错误。
(上图AO(global)。写AO(innerTest)表示此时是进入执行阶段时函数的作用域链)
验证一下?
再来看看我们思维导图:
这里提到了[[scope]],而且为什么作用域链可以表示成 AO+[[scope]] 呢?
首先我们要知道[[scope]]是啥。
借用下汤姆大叔的解释:
看不懂?没事儿,看下面张图(例子还是上一个,没有改变):
现在知道为什么作用域链还可以表示成 AO+[[scope]] 了吧?还没反应过来的再去仔细看看上上张图。
关于ES5规范前的执行上下文相关知识,暂且写到这里。有额外的知识会在之后的文章中再记录。
目前到此为止没有疑惑吗?
我有。
JS代码的整个执行过程不是分编译阶段(编译器完成,将代码翻译成可执行代码)和执行阶段(引擎完成,执行可执行代码)嘛。
下面阐述两个观点:
我们都知道函数执行上下文是在函数调用时创建的。那么都执行到调用函数这步了,引擎肯定已经开始执行可执行代码了。
再来,从创建变量对象过程中包含声明提升我们就知道这应该是在代码执行前进行的,也就是在编译阶段进行的!比如
var a = 2
,var a
代码被扫描,a变量提升,应该是在编译阶段进行的;a = 2
则是在引擎在执行阶段执行。也就是说,创建变量对象过程应该发生在编译阶段,而网上也有文章如是说。这也是尚未进入上下文执行阶段,变量对象VO不能访问其中属性的一种解释。这两个观点有错吗?如果没错,这两个观点矛盾吗?思考下。
我一度觉得矛盾,究矛盾根源就是:我认为JS代码整个执行过程会很直男地从左到右,过了编译阶段进入执行阶段,就永远不能再进编译阶段。而这就与上面两个观点相驳:已经在执行阶段进行到创建函数上下文了,怎么可能又再返回编译阶段呢?
带着疑问各处找资料参阅书籍的时候,最终在书上找到了答案(原话):
ES5规范后
推荐一篇比较权威的译文:【译】理解 Javascript 执行上下文和执行栈
思维导图:
关于这部分,我是之前做了些笔记在笔记本,我拍照贴上来,字不好看但还能看清吧...不想看就跳过哈~这笔记就是个小总结,没啥干货,东西都在那篇译文里。
聪明的你可以自行理解对比下es5前后规范执行上下文的不同与相同之处。
本文完。
希望看到各位技术人对这篇文章有不同的有“证据”,符合逻辑的分析、看法~
觉得写得还不错的,可以点个star支持下作者🖖🏼
The text was updated successfully, but these errors were encountered: