探究JavaScript的运行时

  1. 运行时解析
  2. 总结一下
  3. v8引擎角度看javascript运行时
    1. 编译器和解释器

运行时解析

如下代码

var a = 1
let b = 2
const c = {k: 3}

function d () {
  console.log(a)
  var a = 11
  let b = 22
  dd()
}

function dd () {
   console.log(b)
}

d() // 输出啥? undefined 2

如果你是js老手, 一看d()执行结果就知道是a变量提升了打印undefined,dd方法执行打印2,那a为什么会提升呢, 怎么不是打印外面的变量a = 1呢?b怎么不打印22呢?想要弄清楚这些问题,就要了解js的执行机制了。

js运行时有两个阶段:编译阶段、执行阶段。
编译阶段:js通过编译生成执行上下文和可执行代码两部分。
执行阶段:执行可执行代码,输出结果。
可以理解如下图

js运行时

执行上下文是啥?可执行代码又是啥?

执行上下文是 JavaScript 执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如 this、变量、对象以及函数等。

可执行代码就是指js引擎可以执行的代码,这些代码可以是【字节码、机器码】。我们写的js代码它看不懂,也不会执行,它要先把js编译成字节码、机器码才行。

其实不管是什么高级语言代码都要编译才能执行,后面再说。

了解了这两个概念还不够,执行上下文里面有哪些内容?可以如下图表示

执行上下文

看上图,这是编译阶段的输出,可以了解到一些重要信息:
变量环境:执行上下文中var 声明的变量,且赋值默认值undefined。
词法环境:执行上下文中let const声明的变量,这是解决变量提升问题,重点这是在es6加入的,es5并没有词法环境。
outer: 外部引用,其实在每个执行上下文中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。在js中有全局执行上下文、函数上下文之说,每个上下文的外部引用都是在编译时决定的,这个和代码的编译时的位置有关,也称为词法作用域。词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。理解可以参考下图

词法作用域,编译时根据位置确定

this: this是一套和作用域无关的独立机制,这个你可以另外找到相关文章深入理解。

变量提升其实就是函数运行的编译阶段,把所有var声明的变量统一提升到该作用域“顶部”, 并赋默认值undefined。

所以执行d()函数的时候,相当于以下流程,先编译再执行

var a = undefined
console.log(a)
a = 11

而dd()执行的时候其实就是按照上面说的,由于dd上下文没有声明b变量,根据作用域链查找外部引用,直到首次找到b,而作用域在编译阶段就确定了外部引用。具体可以参考下图:

d方法运行时

所以,就例子而言无论d方法还是dd方法,它们的外部引用都是指向全局上下文,在执行阶段,如果发现函数作用域没有变量声明就会沿着作用域往外部引用查找。

总结一下

  1. js运行时有两个阶段:编译阶段 和 执行阶段
  2. 变量提升的根本原因是js在编译阶段确定的
  3. 执行上下文有变量环境与词法环境,变量环境存储var声明的变量,词法环境存储let cosnt声明的变量。
  4. 每个作用域的外部引用在编译阶段根据代码的位置确定。

v8引擎角度看javascript运行时

上面说到js运行时有编译阶段 和 执行阶段两个阶段,那么再从js引擎的角度来看,这是怎么一回事。

首先先了解一些概念:

编译器和解释器

之所以存在编译器和解释器,是因为机器不能直接理解我们所写的代码,所以在执行程序之前,需要将我们所写的代码“翻译”成机器能读懂的机器语言。

按语言的执行流程,可以把语言划分为编译型语言和解释型语言。编译型语言在程序执行之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译了。比如 C/C++、GO 等都是编译型语言。而由解释型语言编写的程序,在每次运行时都需要通过解释器对程序进行动态解释和执行。比如 Python、JavaScript 等都属于解释型语言。

解释器、编译器 语言流程

了解上面的概念之后,v8引擎就是走的基于解释器的路线,但是又与它不同,是一个改进版。具体如下图

v8引擎处理代码流程

从图中可以看出v8是解释器、编译器一起用了的。

通常,如果有一段第一次执行的字节码,解释器 Ignition 会逐条解释执行。解释器 Ignition 除了负责生成字节码之外,它还有另外一个作用,就是解释执行字节码。在 Ignition 执行字节码的过程中,如果发现有热点代码(HotSpot),比如一段代码被重复执行多次,这种就称为热点代码,那么后台的编译器 TurboFan 就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时,只需要执行编译后的机器码就可以了,这样就大大提升了代码的执行效率。

回到上面的js运行时两个阶段,编译阶段和执行阶段,编译阶段就是源码 ->AST -> 字节码。执行阶段就是基于字节码、机器码执行。

最后
好了,以上就是javascript运行时的内容,了解这一过程希望对你开发过程有所帮助。

转载 小_七


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 chaoyumail@126.com

×

喜欢就点赞,疼爱就打赏