美文网首页
探究javascript的运行时

探究javascript的运行时

作者: 0月 | 来源:发表于2020-07-15 13:39 被阅读0次

    前言

    我们天天写业务,公司小,业务简单,经验不值钱,出来面试被说没亮点,写个业务都要问原理,如果没答好,就开始以下场景:

    面试官:“你有什么要问我的吗?”
    你:“额,我,我没什么要问的。。。”

    不,其实你不是这样子的,你不想这么憋屈的离开,你不想就这样辛辛苦苦跑过来被两三句话打发掉!

    所以,开启绝地反击模式:

    面试官:“你有什么要问我的吗?”
    你:“js的运行时是怎么样的?”
    面试官:“我。。。”

    运行时解析

    如下代码

    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编译成字节码、机器码才行。

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

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


    执行上下文.png

    看上图,这是编译阶段的输出,可以了解到一些重要信息:
    变量环境:执行上下文中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方法运行时.png

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

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

    v8引擎角度看javascript运行时

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

    首先先了解一些概念:

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

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

    解释器、编译器 语言流程

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

    v8引擎处理代码流程.png

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

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

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

    最后

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

    相关文章

      网友评论

          本文标题:探究javascript的运行时

          本文链接:https://www.haomeiwen.com/subject/bxxmcktx.html