【Web技术】1230- 掌握前端框架的 JIT 和 AOT 编译方案
现代前端框架都需要“编译”这一步骤,用于:
将框架中描述的
UI
转换为宿主环境可识别的代码代码转化,比如将
ts
编译为js
、实现polyfill
等执行一些编译时优化
代码打包、压缩、混淆
编译
可以选择放在两个时机执行:
代码构建时,被称为
AOT
(Ahead Of Time,提前编译或预编译),宿主环境获得的是编译后的代码代码在宿主环境执行时,被称为
JIT
(Just In Time,即时编译),代码在宿主环境编译并执行
本文会聊聊两者的区别,及前端框架中AOT
的应用。
AOT和JIT的区别
Angular
同时提供这两种编译方案,下面我们用Angular
举例说明两者的区别。
考虑如下Angular
代码:
import { Component } from "@angular/core";
@Component({
selector: "app-root",
template: "{{getTitle()}}
"
})
export class AppComponent {
public getTitle() {
return 'Hello World';
}
}
定义AppComponent
,最终浏览器(作为宿主环境)渲染的结果为:
现在将模版中使用的getTitle
方法修改为未定义的getTitleXXX
:
// 从
template: "{{getTitle()}}
"
// 修改为
template: "{{getTitleXXX()}}
"
如果使用AOT
,编译后会立刻报错:
如果使用JIT
,编译后不会报错,代码在浏览器中执行时会报错:
造成以上区别的原因是:当使用JIT
时,构建阶段仅仅使用tsc
将ts
编译为js
并将代码打包。
打包后的代码在浏览器运行后,执行到Decorator
(上例中的@Component
语句)时,Angular的模版编译器
才开始编译template
字段包含的模版语法,并报错。
当使用AOT
时,tsc
、Angular的模版编译器
都会在构建阶段进行编译,所以会立刻发现template
字段包含的错误。
除了以上区别外,JIT
与AOT
的区别还包括:
使用
JIT
的应用在首次加载时慢于AOT
,因为其需要先编译代码,而使用AOT
的应用已经在构建时完成编译,可以直接执行代码使用
JIT
的应用代码体积普遍大于使用AOT
的应用,因为在运行时会多出编译器代码
基于以上原因,在Angular
中一般在开发环境使用JIT
,在生产环境使用AOT
。
从前端框架的角度看AOT
可以用两个步骤描述前端框架的工作原理:
根据组件状态变化找到变化的
UI
将
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
优化。
他的思路是:在保持运行结果一致的情况下,改变源代码的运行逻辑,输出性能更高的代码。
即:代码在编译时将计算结果保留在编译后代码中,而不是在运行时才去求值。
比如,如下代码:
(function () {
function hello() { return 'hello'; }
function world() { return 'world'; }
global.s = hello() + ' ' + world();
})();
经由prepack
编译后输出:
s = "hello world";
遗憾的是,由于复杂度以及人力成本考虑,prepack
项目已于三年前暂停了。
Solid.js
同样使用JSX
描述视图,他实现了几个内置组件用于描述UI
的逻辑,从而减少JSX
的灵活性,使AOT
成为可能。比如:
For
替代数组的map
方法:
<For each={state.list} fallback={<div>Loading...div>}>
{(item) => <div>{item}div>}
For>
Show
替代if
条件语句:
<Show when={state.count > 0} fallback={<div>Loading...div>}>
<div>My Contentdiv>
Show>
Switch
、Match
替代switch…case
语句:
<Switch fallback={<div>Not Founddiv>}>
<Match when={state.route === "home"}>
<Home />
Match>
<Match when={state.route === "settings"}>
<Settings />
Match>
Switch>
总结
总结一下,前端框架可以从AOT
中收获很多益处,其中最主要的一条是:
减少“根据
组件状态变化
找到变化的UI
”这一步骤的工作量
要实现AOT
的前提是:组件代码易于分析。
回复“加群”与大佬们一起交流学习~
点击“阅读原文”查看 130+ 篇原创文章