zl程序教程

您现在的位置是:首页 >  前端

当前栏目

JavaScript 脚本编译与执行过程简述

JavaScript执行 过程 脚本 编译 简述
2023-09-27 14:25:57 时间
前言

由于上一篇文章对作用域、执行上下文等写得过于详细 而且参杂了很多内容 看起来并不好理解 因此就简化一下。


正文

在网页里 JavaScript 脚本是插入在 script 标签内的 内联或外部引入 。

 script 

 // some JavaScript code...

 /script 

 script 

 // some JavaScript code...

 /script 

 script src xxx /script 


一般情况下 脚本是按顺序一块一块地执行的 一个 script 标签为一块 。只有执行完一块 才会执行下一块的。如果当前块有语法错误或其他错误导致当前块终止执行 不会影响下一块的执行。


对于外部引入的 JavaScript 脚本 若设置 script 标签的 defer、async 属性会影响脚本的加载顺序 不会按着编写顺序执行。而这些属性对于内联脚本是没有作用的。


要让 JavaScript 代码运行起来 包括编译阶段和执行阶段。每一块脚本的运行都包含这两个过程。请注意 不是将“所有块”编译完再执行的。

编译阶段 

 词法分析 将源码分解成很多个有意义的 token

 语法分析 通篇检查当前块的语法是否有误 若无 将 tokens 转换为 AST

 代码生成 将 AST 转换为 JS 引擎可执行代码

执行阶段 

 创建执行上下文

 初始化执行上下文

 代码执行


JS 引擎的优化工作是在编译阶段完成的 例如 V8 引擎。若编译阶段检查出语法有误 将会终止当前块的后续阶段。


待编译阶段完成后 接着进入执行阶段。而执行阶段可以分为两个步骤


进入执行上下文代码执行


进入执行上下文步骤 可以理解为 真正一行一行执行代码的前期准备工作


JS 引擎创建一个叫做 执行上下文栈 Execution Context Stack ECS 的东西。顾名思义 就是用了维护执行上下文的。
最先进入的肯定是全局代码 这个执行上下文被称为 全局上下文。首先插入 ECS 因此 ECS 栈底永远是全局上下文。后面调用函数时 会进入函数上下文 该上下文也会插入 ECS。
每进入一个执行上下文 都会做一些初始化工作。

待准备工作完成后 就会进入代码执行步骤 即一行一行地去执行代码。

大家可能疑惑的地方 应该是“前期准备工作”的具体过程是怎样的


执行上下文栈


进入全局代码 ECS 插入 GlobalContext 后面每调用一个函数就往 ECS 栈顶插入 FunctionalContext 函数执行完 FunctionalContext 又会从栈顶删除......周而复始的过程。ESC 栈底永远保留着 GlobalContext。直到页面销毁 它才会被删除 ESC 的使命也就结束了 被 JS 引擎清空。

ECS [

 FunctionalContext // 栈顶

 FunctionalContext

 GlobalContext // 栈底

]


前面提到上下文 GlobalContext 全局上下文 、FunctionalContext 函数上下文 。


执行上下文


其实 JavaScript 中有三种执行上下文

全局上下文 

 全局下都是属于全局上下文。

函数上下文 

 顾名思义 就是 JavaScript 函数内的执行上下文

eval 上下文 

 就是 eval( some code... ) 尽可能不要用 会影响性能、欺骗词法作用域


每个执行上下文 可以看作是一个 JavaScript 对象 它有三个重要属性

Variable Object: 

 即变量对象 简称 VO。我们声明的变量、函数就是放在 VO 中的。

Scope Chain: 

 即作用域链 简称 Scope。它包含了当前上下文 VO 以及各父级的 VO。查找变量就是根据这链条去查询的。

This: 

 即 this 对象 这是一个很灵活的对象 跟函数调用相关。

}


执行上下文的初始化工作 就是确定这些属性的一些值。这个“初始化”的过程也常被称为“预编译” 网上很多文章都有这个说法。


举例


上面的执行阶段的过程 举例会更好地说明。

var a global 

function foo() {

 var a local 

 function bar() {

 console.log(a)

 bar()

foo() // local 


当编译过程之后 进入执行阶段。JS 创建一个执行上下文栈 ECStack 总是先进入全局上下文 全局上下文被插入到执行上下文栈

ECStack [ GlobalContext ]


接着对 GlobalContext 进行初始化

GlobalContext {

 VO: window, 

 Scope: [ GlobalContext.VO ],

 this: GlobalContext.VO

// 注意 不同宿主环境顶层对象是不一样的。

// 浏览器下为 window 对象 Node 中为 global 对象。

// 本文以浏览器为例 因此直接写了 window 对象。


初始化的同时 foo 函数也被创建 会保存当前上下文的作用域链到函数内部的 [[scope]] 属性中。该属性可以通过 console.dir(foo) 查看。

foo.[[scope]] [ GlobalContext.VO ]


当全局上下文初始化完成之后 开始执行全局代码 当进行到 foo() 时 JS 引擎又会创建 foo 函数执行上下文 并插入 ECStack 栈顶。

ECStack [ GlobalContext, FunctionalContext foo ]


跟着 会进入 foo 函数上下文 该上下文也会进行初始化工作


复制 foo 函数 [[scope]] 属性创建作用域链。用 arguments 创建活动对象 AO。初始化活动对象 即 AO 按顺序加入 形参、函数声明、变量声明。若存在同名情况 函数声明会覆盖形参 变量声明会被忽略。将 AO 对象加入 foo 作用域链顶端。

FunctionalContext foo {

 AO: {

 arguments: {

 length: 0

 a: undefined,

 bar: ƒ bar()

 Scope: [ GlobalContext.VO, AO ],

 this: undefined // this 是在函数执行的时候才确定下来的 foo 函数的 this 的值跟作用域链没有关系

}


初始化完成 开始执行 foo 函数体代码 AO 对象也会更新。当执行到 bar() 函数时 又将会进入 bar 函数上下文 并进行初始化工作

ECStack [ GlobalContext, FunctionalContext foo , FunctionalContext bar ]

FunctionalContext bar {

 AO: {

 arguments: {

 length: 0

 Scope: [ GlobalContext.VO, FunctionalContext foo .VO, AO ],

 this: undefined

}


初始化完成 开始执行 bar 函数代码 在 console.log(a) 中 会查找 a 变量。


按作用域链顺序查找 先从当前上下文的 AO 中查找 若查找不到再往上一层 AO 中查找...直到全局上下文的 VO 中查找 若再查找不到就会抛出引用错误 ReferenceError 。

AO - FunctionContext foo .AO - GlobalContext.VO


很明显 a 变量将会在 FunctionContext foo .AO 中查找到 其值为 local 因此打印结果就是 local 。


待 bar() 执行完毕 bar 函数上下文会被从栈顶删除。

ECStack.pop(FunctionalContext bar )


又将执行权交还给 foo 函数上下文 它也将会执行完毕 也会从栈顶移除。

ECStack.pop(FunctionalContext foo )


然后又交还给全局上下文

ECStack [ GlobalContext ]


至此 这个程序执行完毕。当页面销毁时 ECStack 才会被清空。


分享8个可以编译为JavaScript的语言 JavaScript 并不是最好的语言,特别是在复杂的应用中,它可能不太能胜任。为了避免这种情况,一些新的语言或现有语言的编译器被创造出来,不用写一行 JavaScript 或者考虑这种语言的局限,就能生产在浏览器能运行的代码。
八、通过断点调试观察JS执行过程 利用chrome开发者工具中的断点调试,我们能够一步步观察JavaScript的执行过程,直观感知函数调用栈、作用域链、变量对象、闭包、this等关键信息的变化。因此,断点调试对于快速定位代码错误,以及快速了解代码的执行过程有着非常重要的作用,这也是我们前端开发必不可少的一个高级技能。 当然如果你对JavaScript的基础概念(执行上下文,变量对象,闭包,this等)了解还不够的话,想要透彻掌握断点调试可能会有一些困难。好在前面几篇文章中,我都对这些概念进行了详细的概述,因此要掌握这个技能,对大家来说,应该是比较轻松的。