JavaScript 为什么快--第二篇

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 上一篇,我们介绍了 V8 引擎的执行管道架构。本篇将着重介绍 V8 的语法解析过程。原视频上一篇是产品经理思维;本篇则是理工科思维;语法解析阶段对于前端来说尤其重要,相对 Noder 来说较弱,因为 parser 只会影响应用启动和前期的运行阶段。

上一篇,我们介绍了 V8 引擎的执行管道架构。本篇将着重介绍 V8 的语法解析过程。原视频
上一篇是产品经理思维;本篇则是理工科思维;
语法解析阶段对于前端来说尤其重要,相对 Noder 来说较弱,因为 parser 只会影响应用启动和前期的运行阶段。
对于前端同学来说,经常习惯性的引入一些很大的库,而只使用了其中1,2个函数。例如 lodash。这样对性能的影响到底有多大?

还是结论先行

  1. V8的语法解析有2种模式:eager 解析器(全面)和 lazy 预解析器(快速)。虽然 lazy 解析比 eager 快一倍,但是lazy可能导致需要1.5倍的解析时间;(lazy 预解析后,还需要 eager 解析一次)。你可以用Optimize.js强制 eager 运行
  2. JavaScript 的语法解析速度为:1MB/S。解析400k JavaScript,需要大概370ms。可以通过 chrome 浏览器地址栏 chrome://tracing 查看具体时间;
  3. 前端页面运行的 JavaScript 代码尽量少;解析器也有缓存,缓存字节码,如果采用 bundle 的话,更新 bundle 会导致整个 bundle 失效。

计算机编译原理简单介绍

由于本篇需要部分计算机编译原理背景知识。所以感觉需要补充一下,计算机编译原理,将人能读懂的代码转换成机器能读懂的代码,机器执行时只认识机器语言指令。
通常计算机高级语言都需要经过:源程序->语法解析->中间代码生成->代码优化->目标代码生成->目标程序。对应V8也不例外:JS源代码->语法解析->生成字节码->编译器->转化器->运行代码。
语法解析阶段生成语法树和作用域,就是将我们的每行代码变成语法树状结构,来消除歧义,代码分析,绑定作用域。
可以通过esprima看看JavaScript的语法树什么样子:http://esprima.org/demo/parse.html#

本篇主要介绍V8的语法解析过程,产物就是字节码(中间代码)。下一篇介绍V8的编译器运行。

JavaScript 语法解析 - lazy 要比 eager 好吗?

什么是 JavaScript 语法解析?

我们从上一篇的 JavaScript 执行管道,下图红色的部分就是语法解析的过程。实际就是 JavaScript 的编译阶段。虽然编译过程不参与“ JavaScript 的运行阶段(下图蓝色部分)”,但作为动态脚本语言,JavaScript 的解析在代码变更和实际运行时,还是会触发语法解析的。

15335656973482

我们为何要关心解析?

  • 一个典型的单页 Web 应用:

    • 需要加载0.4MB的 JavaScript;
    • 大约耗时370毫秒;(在手机型号 Moto G4 测试)
  • ->语法解析的速度 ~ 1MB/s

V8 是如何处理 JavaScript 语法解析的? eager parse & lazy parse

这是 V8 的自己实现,为了提升 JavaScript 文件的语法解析速度;目前非 JavaScript 引擎的官方规范。

  • 2种解析模式: eager (全面解析模式) 和 lazy (快速解析模式)
  • 为什么解析 JavaScript 代码那么难?

2种解析器

  • 解析器: 全面解析模式, "eager"

    • 用于解析我们想编译的函数;
    • 构建语法树;
    • 构建函数作用域(Scopes);
    • 找出所有语法错误;
  • 预-解析器: 快速解析模式, "lazy"

    • 用于跳过我们不想编译的函数们;
    • 不构建语法树,会构建函数作用域,但不设置函数作用域中的变量引用(variable references)和变量申明(variable declarations);
    • 解析速度,大约比eager解析器快2倍
    • 找出限定的几种错误(没有遵守JavaScript的规范)

Lazy or eager?

lazy 预编译由前2位首字母决定;所以如果我们想跳过 lazy 触发 eager 编译,我们应该在前面加位操作符,例如'!|~'。我们直接看代码:

let a = 0; //顶层的代码都是 eager
// 立即执行函数表达式 IIFE = Immediately Invoked Function Expression
(function eager() {...})(); // 函数体是 lazy
// 顶层的函数非IIFE
function lazy() {...} // 函数体是 lazy
// 后续执行时
...
lazy(); // ->eager 开始解析和编译!
// 启示,通过这种方式触发eager解析
!function eager2() {...}, function eager3() {...} // All eager
 
// 错误的case!
let f2 = function lazy() {...}(); // 先触发了lazy 解析, 然后又eager解析

Lazy 和 Eager 为什么都很重要?

  • 我们需要lazy解析器, 因为web页面会使用很多无关代码;(事实,摆手)
  • 如何选择呢?

    • 如果我们eager解析了我们无关代码,我们在浪费时间;
    • 如果我们lazy解析了我们有关代码,我们将多支付预解析的时间:0.5 x 解析时间 + 1 x 解析时间 = 1.5 解析时间
  • 假设我们只知道我们的启动代码,并不知道具体会执行哪些代码。(事实again,摆手)

强迫执行 eager 解析

  • Optimize.js 用括号括住它认为将被执行的函数。
浏览器 使用 optimize-js 后通常启动速度提升
Chrome 55 20.63%
Edge 14 13.52%
Firefox 50 8.26%
Safari 10 -1.04%
  • 实际上我们只需要

    1. 解析编译正确的函数;
    2. 最小化我们失败的代价;
  • 在此基础上迭代

Web 开发者如何利用 V8 的解析器?

使用更少的代码!

  • JavaScript 的启动性能;
  • 使用更少的 JavaSCript: 使用 Chrome Dev Tools 的 code coverage 功能;
  • 衡量你的代码解析开销:chrome://tracingv8.runtime_stats

代码缓存 + Bundling

  • 代码缓存: V8 会缓存经常使用的 JavaScript 的字节码;
  • Bundling: 如果你更新了 bundle 的一部分代码,将失去整个 bundle 缓存;
  • 避免使用 eval

Web 开发者: 使用 streaming

  • 流式 JavaScript: 并行下载和解析;
  • 体积大的 JavaScripts

    • 尽可能早的异步读取;
    • 确保流式 JavaScript 运转 chrome://tracing

括号黑魔法

  • 使用括号技巧选中需要 eager 解析并编译的关键路径:

    • 旧版本的 Chrome;
    • 跨浏览器;
    • 现在就要提升性能,立刻马上!(等不及我们去修复)

额外内容

  • V8 解析器是一款 V8 递归下降编译器;
  • 大约~15k 行C++ 还有 ~7k 行C, for the AST+Scopes
目录
相关文章
|
2天前
|
资源调度 JavaScript 前端开发
JavaScript进阶 - JavaScript库与框架简介
【7月更文挑战第5天】JavaScript库和框架构成了前端开发的核心,如jQuery简化DOM操作,Angular、React和Vue提供全面解决方案。选择时要明确需求,避免过度工程化和陡峭学习曲线。使用版本管理工具确保兼容性,持续学习以适应技术变化。示例展示了jQuery和React的简单应用。正确选择和使用这些工具,能提升开发效率并创造优秀Web应用。
|
17天前
|
JavaScript 前端开发 索引
第四篇-Javascript函数
第四篇-Javascript函数
13 3
|
2月前
|
JavaScript 前端开发 UED
JavaScript笔记+案例(上)
JavaScript笔记+案例
58 0
|
2月前
|
JavaScript 前端开发
JavaScript笔记+案例(下)
JavaScript笔记+案例
44 0
|
11月前
|
存储 前端开发 JavaScript
JavaScript 基础(5) - 笔记
JavaScript 是 Web 的编程语言。 所有现代的 HTML 页面都可以使用 JavaScript。 学习从初级到高级 JavaScript 知识。
197 2
|
11月前
|
JSON JavaScript 前端开发
第一章 JavaScript --上
第一章 JavaScript --上
43 0
|
11月前
|
JavaScript 前端开发 API
第一章 JavaScript --下
第一章 JavaScript --下
36 0
|
Web App开发 JSON 自然语言处理
JavaScript基础系列开篇:V8是如何运行JavaScript(let a = 1)代码的?
我们知道,机器是不能直接理解我们平常工作或者自己学习的代码的。所以,在执行程序之前,需要将代码翻译成机器能读懂的机器语言。按语言的执行流程,可以把计算机语言划分为编译型语言和解释型语言
125 0
|
存储 JavaScript 前端开发
JavaScript基础第01天笔记(下)
JavaScript基础第01天笔记(下)
124 0
JavaScript基础第01天笔记(下)
|
存储 Web App开发 编解码
JavaScript基础第01天笔记(上)
JavaScript基础第01天笔记
115 0
JavaScript基础第01天笔记(上)