JavaScript执行原理

13 篇文章 1 订阅

本文章知识来源b站upobjtube的卢克儿

1. JS是如何被编译的

JavaScript初认识

JavaScript是由Brenddan Eich在1995年创建的,JavaScript在初期设计时基本就是很多语言的大杂烩

  • 借鉴了C语言的基本语法
  • 借鉴了Java语言的数据类型和内存管理
  • 借鉴了Schema语言将函数提升到”第一等公民“的地位
  • 借鉴了Self语言使用了基于原型prototype的继承机制

JavaScript实际上是函数式编程+面向对象编程的混合产物,在JavaScript中我们定义变量是完全不用关系他的类型的,但是像C++这样的就必须规定变量的类型,所以JavaScript被称为动态类型语言。

  • 在JavaScript里,我们可以在声明赋值之后可以随意添加、删除里面的属性。
  • 因为源代码里给编译器提供的信息太少了,使得编译器没有办法在运行前知道变量的类型
  • 只有在运行期间才能确定各个变量的类型,这就导致JS无法在运行前编译出更加迅速的低级语言代码->机器代码
  • 相反,使用c++这些语言时,因为提供了足够的类型信息来帮助编译器编译出机器代码
JIT

尽管JavaScript是一门动态语言,但是现在的JavaScript运行起来依然是很迅速。原因:现在的JS引擎都使用了一项技术Just-In-Time Compilation(运行时编译),简称JIT,一种提高程序运行效率的方法

  • JIT就是在运行阶段生成机器代码,而不是提前生成
  • JIT把运行代码和生成机器代码结合在一起,在运行阶段收集类型信息,然后根据这些信息编译生成机器码
  • 之后在运行这些代码就直接使用生产好的机器代码
AOT

AOT是另一种方法,在运行前提前生成好机器代码,比如像C++这样的语言

JS引擎

JS作为高级程序语言,在被计算机CPU执行前,需要通过JS引擎将JS转换成低级的机器语言并执行

JS引擎列举:

  • 谷歌Chrome使用的V8引擎

  • webkit使用的JavaScriptCore

  • Mozilla的SpiderMonkey

JS引擎编译JS的流程

  • 首先将JS源码通过解析器,解析成抽象语法树AST
  • 接着在通过解释器将AST编译成字节码bytecode
  • 字节码在通过编译器生成机器代码(汇编代码),编译器会根据不同的平台编译出相应的机器代码

2. JS引擎

1. 介绍
用到V8引擎的程序
  • Chrome浏览器的JS引擎是V8
  • Nodejs的运行时环境是V8
  • electron的底层引擎是V8 -> 跨平台桌面应用开发工具

拓展

  • blink是渲染引擎,V8是JS引擎

  • 访问Dom的接口是由Blink提供的

功能

接收JavaScript代码,编译代码后执行C++程序,编译后的代码可以在多种操作系统多种处理器上运行

  • 编译和执行JS代码
  • 处理调用栈
  • 内存分配
  • 垃圾回收
2. V8对于JS的编译和执行

大部分JS引擎在编译和执行JS代码时,都会用到三个重要的组件:解析器、解释器、编译器

解析器parser
  • 负责将js源代码解析成抽象语法树AST
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bMKGWkWy-1635753840958)(C:\Users\xmydd\AppData\Roaming\Typora\typora-user-images\image-20211101135024796.png)]
解释器interpreter
  • 负责将AST解释成字节码bytecode
  • 解释器也有直接解释执行bytecode的能力(跳过AST
    在这里插入图片描述
编译器compiler
  • 负责编译出运行更加高效的机器代码
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RInKbEQd-1635753840974)(C:\Users\xmydd\AppData\Roaming\Typora\typora-user-images\image-20211101135622912.png)]
3. 早期的V8引擎

在V8早期5.9版本之前,V8引擎没有解释器,有两个编译器

编译流程

JS代码 --(解析器parser)–> AST抽象语法树 --(Full-codegen编译器)–> 机器代码

  • Full-codegen编译器:也称为基准编译器,他生成的是一个基准的未被优化的机器代码
  • 这样做的好处就是,当第一次执行js代码时就是直接使用了高效的机器代码,因为没有中间字节码的产生
  • 当按照上面的流程代码运行一段时间后,V8引擎中的分析线程收集了足够的数据来帮助另一个编译器Crankshaft来做代码优化
  • 需要优化的源码重新解析生成AST
  • Crankshaft使用生成好的AST再生成优化后的机器代码,从而提升运行的效率
  • 完整步骤
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bX4A7yUC-1635753840984)(C:\Users\xmydd\AppData\Roaming\Typora\typora-user-images\image-20211101140822118.png)]
评价
  • 好处:
    • 减少了抽象语法树AST到字节码的转换时间,提高外部浏览器中JS的执行性能
  • 弊端:
    • 生成的机器码会占用大量的内存。对于早期低内存的安卓设备来说是不能承受的
    • 缺少中间层机器码,很多性能优化策略无法实施,导致V8引擎性能提升缓慢
    • 用到的编译器无法很好的支持和优化JS的新语法特性
4. 改进后的V8引擎
编译流程

JS代码 --(parser解析器)–> AST --(lgniton基准解释器)–> bytecode字节码 ----> AST被清除 ----> bytecode直接被解释器执行 --(lgniton收集代码优化信息)–> TruboFan编译器

  • 新增了lgniton基准解释器
  • 在生成bytecode字节码的同时,AST被清除掉,释放内存空间
  • 生成的bytecode直接被解释器执行
  • 同时生成的bytecode将作为基准执行模型,字节码更加简洁
  • 生成的bytecode大小相当于等效的基准机器代码的25%到50%左右
  • 在代码的不断的运行过程中,解释器收集到了很多,可以用来优化代码的信息
    • 比如变量的类型、哪些函数执行的频率较高
  • 这些信息被发送给优化编译器TruboFan
  • TruboFan编译器会根据这些信息和字节码,来编译出经过优化的机器代码
  • 优化过后得到的机器代码可能会被逆向还原成字节码,这个过程叫做deoptimization
    • 这是因为JavaScript是一个动态语言,会导致一个lgnition收集到的信息是错误的
    • 比如有这样的一个sum函数,在函数声明时,JS引擎并不知道参数x,y是什么类型,但在后面的多次调用中,传入的x,y都是整型,sum函数被识别为热点函数
    • 解释器将收集到的类型信息和该函数对应的字节码发送给编译器
    • 于是编译器生成的优化后的机器代码中就假定了sum函数的参数x,y都是整型
    • 之后遇到该函数的调用,就直接使用运行更快的机器代码
    • 如果此时你调用sum函数传入了字符串,机器代码不知道如何处理字符串的参数
    • 于是就需要进行deoptimization,也就是回退字节码,由解释器来解释执行
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ix4RxbdX-1635753840988)(C:\Users\xmydd\AppData\Roaming\Typora\typora-user-images\image-20211101142517604.png)]
V8引擎的优化策略
  • 函数只声明未被调用,不会被解析生成AST
  • 函数只被调用一次,则Ignition生成字节码后,bytecode直接被解释执行,Turbofan不会进行优化编译
    • 因为它需要lgnition收集函数,执行时的类型信息,这就要求函数至少执行大于一次,TurboFan才能够进行优化
  • 函数被调用多次,可能会被标记为热点函数,当lgnition解释器收集的类型信息确定后,这时TurboFan则会将bytecode编译为优化后的机器代码,以提高代码的执行性能。之后执行这个函数时,就直接运行优化后的机器代码
  • 由于不需要一开始直接编译成机器码,而是生成了中间层的字节码,字节码的生成速度是远远大于机器码的,所以网页初始化解析执行JS的时间缩短了,缩短了网页加载的时间
  • 在生成的优化机器代码时,不需要从源码重新编译,而使用字节码。并且当需要deoptimization时,只需要回归到中间层的字节码解释执行就可以了

3. JS调用栈

1. 什么是调用栈
  • 调用栈是JS引擎追踪函数执行流程的一种机制,当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体又调用了哪个函数。

  • 它采用了先进后出的机制来管理函数的执行

  • 举个栗子:

    function sum(a, b){
        return a + b;
    }
    
    function average(a, b){
        const aver = sum(a,b) / 2;
        return aver / 2;
    }
    
    const num = average(3, 5);
    console.log(num)
    
    • 函数的声明是不会放入栈中的,调用栈,顾名思义一定是被调用的函数才会入栈

    • 所以直到运行到const num的时候average()函数才被调用

    • 把average的执行添加到调用栈,然后执行average函数体中的所有代码
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D82od6Wq-1635753840991)(C:\Users\xmydd\AppData\Roaming\Typora\typora-user-images\image-20211101145424275.png)]

    • 当进入average函数体中发现调用了sum()函数,接着将sum()函数入栈 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWGpFlrU-1635753840993)(C:\Users\xmydd\AppData\Roaming\Typora\typora-user-images\image-20211101145532244.png)]

    • 执行sum()函数体中的代码,直到sum()函数执行完毕,然后继续执行sum函数后面的代码

    • 接着sum函数出栈,从栈顶移除。等到average函数执行完毕,等到执行average函数后面的代码,average函数出栈。

    • 最后执行console.log()console.log()入栈,执行完毕console.log()出栈

    • 调用栈被清空

查看某一行代码所处的调用栈环境

介绍两种方法

  • 给某一行代码打断点
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mW2dVAEH-1635753840996)(C:\Users\xmydd\AppData\Roaming\Typora\typora-user-images\image-20211101150834754.png)]
  • 抛出异常
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kS8HeIgV-1635753840999)(C:\Users\xmydd\AppData\Roaming\Typora\typora-user-images\image-20211101150901365.png)]
2. 堆栈溢出

触发堆栈溢出最常见的就是递归

举一个栗子:

function sum(){
    sum()
}
sum()
  • 如上面的函数,sum函数每被调用一次就执行sum函数,sum函数被推入调用栈,sum函数里又调用sum函数,由于没有停止条件,sum函数就会不断的被推进调用栈

  • 在很短的一个时间内就会超出调用栈的一个堆栈限制,这种行为被称为溢出overflowing

  • 在控制台会展示这样一个错误,告诉你当前调用栈已经超出了最大使用范围,然后网页就会卡死
    在这里插入图片描述

栈溢出带来的问题

当前浏览器标签页里的JS执行出现了溢出,会影响其他标签页,原因如下:

  • JavaScript的执行环境是一个单线程,这也就意味着JS环境只有一个调用栈
  • 如果调用栈的某个函数执行需要消耗大量时间的话,就会导致调用栈被阻塞,无法入栈和出栈
  • JS页面的绘制和布局都是在一个主线程里,如果JS执行迟迟不归还主线程的话,就会影响页面的渲染,就可能会导致页面出现卡顿的现象
优化这个问题的解决方案
  • 使用事件循环和异步回调
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值