前端框架的JIT与AOT,傻傻分不清楚

开发 前端
本文会聊聊两者的区别,及前端框架中AOT的应用。Angular同时提供这两种编译方案,下面我们用Angular举例说明两者的区别。

[[433991]]

大家好,我卡颂。

现代前端框架都需要“编译”这一步骤,用于:

  • 将框架中描述的UI转换为宿主环境可识别的代码
  • 代码转化,比如将ts编译为js、实现polyfill等
  • 执行一些编译时优化
  • 代码打包、压缩、混淆

编译可以选择放在两个时机执行:

  • 代码构建时,被称为AOT(Ahead Of Time,提前编译或预编译),宿主环境获得的是编译后的代码
  • 代码在宿主环境执行时,被称为JIT(Just In Time,即时编译),代码在宿主环境编译并执行

本文会聊聊两者的区别,及前端框架中AOT的应用。

AOT和JIT的区别

Angular同时提供这两种编译方案,下面我们用Angular举例说明两者的区别。

考虑如下Angular代码:

  1. import { Component } from "@angular/core"
  2.  
  3. @Component({ 
  4.   selector: "app-root"
  5.   template: "<h3>{{getTitle()}}</h3>" 
  6. }) 
  7. export class AppComponent { 
  8.   public getTitle() { 
  9.     return 'Hello World'
  10.   } 

定义AppComponent,最终浏览器(作为宿主环境)渲染的结果为:

现在将模版中使用的getTitle方法修改为未定义的getTitleXXX:

  1. // 从 
  2. template: "<h3>{{getTitle()}}</h3>" 
  3. // 修改为 
  4. template: "<h3>{{getTitleXXX()}}</h3>" 

 如果使用AOT,编译后会立刻报错:

ERROR occurs in the template of component AppComponent.

如果使用JIT,编译后不会报错,代码在浏览器中执行时会报错:

ERROR TypeError: _co.getTitleXXX is not a function

造成以上区别的原因是:当使用JIT时,构建阶段仅仅使用tsc将ts编译为js并将代码打包。

打包后的代码在浏览器运行后,执行到Decorator(上例中的@Component语句)时,Angular的模版编译器才开始编译template字段包含的模版语法,并报错。

当使用AOT时,tsc、Angular的模版编译器都会在构建阶段进行编译,所以会立刻发现template字段包含的错误。

除了以上区别外,JIT与AOT的区别还包括:

  • 使用JIT的应用在首次加载时慢于AOT,因为其需要先编译代码,而使用AOT的应用已经在构建时完成编译,可以直接执行代码
  • 使用JIT的应用代码体积普遍大于使用AOT的应用,因为在运行时会多出编译器代码

基于以上原因,在Angular中一般在开发环境使用JIT,在生产环境使用AOT。

从前端框架的角度看AOT可以用两个步骤描述前端框架的工作原理:

  1. 根据组件状态变化找到变化的UI
  2. 将UI变化渲染为宿主环境的真实UI

借助AOT对模版语法编译时的优化,就能减少步骤1的开销。

这是大部分采用模版语法描述UI的前端框架都会进行的优化,比如Vue3、Angular、Svelte。

其本质原因在于模版语法的写法是固定的,固定意味着「可分析」。

「可分析」意味着在编译时可以标记模版语法中的静态部分(不变的部分)与动态部分(包含自变量,可变的部分),使步骤1在寻找变化的UI时可以跳过静态部分。

甚至Svelte、Solid.js直接利用AOT在编译时建立了「组件状态与UI中动态部分的关系」,在运行时,组件状态变化后,可以直接执行步骤2。

AOT与JSX

而采用JSX描述UI的前端框架则很难从AOT中受益。

原因在于JSX是ES的语法糖,作为JS语句只有执行后才能知道结果,所以很难被静态分析。

为了让使用JSX描述UI的前端框架在AOT中受益,有两个思路:

  • 使用新的AOT思路
  • 约束JSX的灵活性

React尝试过第一种思路。prepack是meta(原Facebook)推出的一款React编译器,用来实现AOT优化。

[[433994]]

他的思路是:在保持运行结果一致的情况下,改变源代码的运行逻辑,输出性能更高的代码。

即:代码在编译时将计算结果保留在编译后代码中,而不是在运行时才去求值。

比如,如下代码:

  1. (function () { 
  2.   function hello() { return 'hello'; } 
  3.   function world() { return 'world'; } 
  4.   global.s = hello() + ' ' + world(); 
  5. })(); 

经由prepack编译后输出:

  1. s = "hello world"

遗憾的是,由于复杂度以及人力成本考虑,prepack项目已于三年前暂停了。

Solid.js同样使用JSX描述视图,他实现了几个内置组件用于描述UI的逻辑,从而减少JSX的灵活性,使AOT成为可能。比如:

For替代数组的map方法:

  1. <For each={state.list} fallback={<div>Loading...</div>}> 
  2.   {(item) => <div>{item}</div>} 
  3. </For

 Show替代if条件语句:

  1. <Show when={state.count > 0} fallback={<div>Loading...</div>}> 
  2.   <div>My Content</div> 
  3. </Show> 

总结

总结一下,前端框架可以从AOT中收获很多益处,其中最主要的一条是:

减少“根据组件状态变化找到变化的UI”这一步骤的工作量

要实现AOT的前提是:组件代码易于分析。

 

责任编辑:姜华 来源: 魔术师卡颂
相关推荐

2021-03-10 08:56:37

Zookeeper

2021-07-27 07:31:16

JavaArrayList数组

2022-05-15 21:52:04

typeTypeScriptinterface

2024-02-29 09:08:56

Encoding算法加密

2020-10-30 08:20:04

SD卡TF卡存储

2018-05-22 16:24:20

HashMapJavaJDK

2018-12-17 12:30:05

Kubernetes存储存储卷

2020-03-03 17:35:09

Full GCMinor

2023-02-27 15:46:19

数据元元数据

2023-09-03 21:18:07

Python编程语言

2021-02-08 23:47:51

文件存储块存储对象存储

2016-11-04 12:51:46

Unix网络IO 模型

2022-02-25 09:14:33

类变量共享实例变量

2021-02-14 22:33:23

Java字符字段

2020-11-11 07:32:18

MySQL InnoDB 存储

2021-01-13 08:10:26

接口IEnumeratorIEnumerable

2023-04-11 15:57:49

JavaScriptCSSHTML

2021-12-29 07:34:40

Filter CSS backdrop-fi

2019-11-21 14:22:12

WiFiWLAN区别

2021-06-07 09:20:56

Javascript运算符开发
点赞
收藏

51CTO技术栈公众号