理解前端框架中的运行时和编译时

本文主要阐述前端框架中的运行时编译时 的区别。首先说明本文内容参考书籍 《Vue.js 设计与实现》,并加以自己的理解创作。有错误的地方还请指出。

运行时

运行时就是指代码实际执行时的阶段。前端代码是在浏览器中执行的,换言之,如果一个框架的代码可以直接在浏览器中执行,那它就是一个纯运行时的框架。

举个例子,假设我们设计了一个框架,它提供一个 Render 函数,用户使用时,为该函数提供一个描述 DOM 树形结构的参数对象,然后 Render 函数根据该对象递归地将数据渲染成 DOM 元素。假设规定要传入的对象结构如下:

javascript 复制代码
const obj = {
  tag: "div",
  children: [
    {
      tag: "span",
      children: "hello world!",
    },
  ],
};

对象中有两个属性:tag 代表标签名称,children 可以是数组(代表子节点)也可以是文本(代表文本子节点)。然后实现 Render 函数:

javascript 复制代码
function Render(obj, root) {
  const el = document.createElement(obj.tag);
  if (typeof obj.children === "string") {
    const text = document.createTextNode(obj.children);
    el.appendChild(text);
  } else if (obj.children) {
    obj.children.forEach((child) => Render(child, el));
  }

  root.appendChild(el);
}

现在可以在浏览器环境中调用 Render 函数:

javascript 复制代码
// 渲染到 body 下
Render(obj, document.body);

此时就会看到预期的结果。这就是一个纯运行时的框架例子,因为浏览器可以直接运行 JavaScript 文件,所以它不需要任何的转换操作。

编译时

了解了运行时,那编译时就很好理解,它是指将源代码转换成可执行代码的阶段。在前端中,最简单的例子就是将高级的 ES6 语法转换为浏览器可以理解和执行的 ES5 语法,这个过程也是编译。

还是拿上面的代码示例来说明,如何将它改造成编译时的框架?但是首先我们需要知道,不是为了有编译时而加编译时,而是为了解决问题才有的编译时。

我们来看上面运行时的代码中有什么问题?很显然就是定义 DOM 树形结构对象有点麻烦,如果 DOM 结构很复杂,需要定义的对象就会嵌套很深,抽象不直观。所以我们在想可以直接像写 HTML 一样来声明 UI 吗?对,就是 vue 中模板(template)那样编写。首先我们应该知道,vue 文件中的 template 部分并不是 html,虽然长得很像,但是浏览器是不能直接识别它们的,所以 template 模板是需要编译的。那么编译成什么呢?

编译的结果是为了让浏览器能直接运行,那么假设我们的模板这样写:

html 复制代码
<div>
  <span>hello world!</span>
</div>

记住它不是 html 文件,所以我们需要将它编译成命令式代码:

javascript 复制代码
const div = document.createElement("div");
const span = document.createElement("span");
span.innerText = "hello world!";
div.appendChild(span);
document.body.appendChild(div);

此时浏览器就可以直接执行代码并达到预期效果了。将这个过程封装成一个编译器,用户所有的代码只能通过编译器编译后才能执行,那它就是一个纯编译时框架,代表框架如 Svelte

运行时 + 编译时

上面提到 vue 中的模板需要编译才能被浏览器执行,那 vue 是一个编译时框架吗?答案是否的。

vue 是一个运行时 + 编译时的框架。这个在官方文档中有提及运行时 vs 编译时响应性,文档中说到:

Vue 的响应式系统基本是基于运行时的。追踪和触发都是在浏览器中运行时进行的。

这里先不管 vue 中的实现细节。我们还是拿上面的例子说明,如果它是运行时+编译时,应该怎么实现?

其实就是加一个编译器就行,但不是直接编译成命令式创建代码,而是将模板编译成一个 DOM 描述对象,然后将对象传入渲染器。就是这样:

html 复制代码
<div>
  <span>hello world!</span>
</div>

编译成 ⬇️

javascript 复制代码
const obj = {
  tag: "div",
  children: [
    {
      tag: "span",
      children: "hello world!",
    },
  ],
};

然后渲染:

javascript 复制代码
Render(obj, document.body);

这就是一个运行时+编译时的过程,准确的说是一个运行时编译,就是代码执行时才编译。在实际框架使用中,我们可以在构建过程中执行编译,等真正运行时就无需编译,这样性能更好。

代码写到这,我们可能会想这样的运行时编译是不是多此一举,直接编译彻底不是更省事吗?理论上纯编译时的性能确实会更好,不需要运行时参与,代码直接编译成可执行的 JavaScript 代码,但是这也散失了部分灵活性,用户的内容必须编译后才能使用。而纯运行时,由于没有编译过程,就没办法分析用户提供的内容,从而不能做对应的优化,一般编译器中我们可以实现语法分析,优化和转换,可以执行语法错误检查,模块打包和代码压缩等任务。只能说框架作者在设计框架时,都是有自己的取舍,不能直接判断哪种更好。

使用框架 or 直接写 html

看到上面分析的一堆,我们不禁会想抛弃框架直接写 html 不是更直观省事。但事实我们也感受到了,使用 vue 或 react 这样的现代框架,能够大大增加我们的开发效率。原因就在于这些框架帮我们封装好了做事的过程,如果是传统原生 html 开发,我们需要维护实现目标的整个过程,包括要手动完成 DOM 元素的创建、更新、删除等工作。但框架让我们只需要为结果声明 UI,不需要关心实现过程。这就是框架的便利之处,了解运行时和编译时也是为了更好的了解框架的实现原理。

结尾

最后对运行时编译时做一个总结。

运行时(Runtime)是代码实际执行时的阶段。在前端框架中,运行时通常指在浏览器环境中加载和执行已经编译好的代码。这个阶段包括了解析 HTML 结构、构建 DOM 树、执行 JavaScript 代码以及处理用户交互等任务。在运行时,前端框架会利用编译时生成的代码来动态地更新和渲染页面,处理事件和数据的变化等。

编译时(Compile Time)是指在开发过程中,将开发者编写的源代码转换为可执行的代码的阶段。例如 es6 转 es5,vue 模板转成 html 等。在编译时,前端框架的编译器会对代码进行语法分析、优化和转换,以生成可在浏览器中运行的代码。编译时的任务包括语法检查、模块打包、代码压缩等。

相关推荐
web小白成长日记3 分钟前
前端让我明显感受到了信息闭塞的恐怖......
前端·javascript·css·react.js·前端框架·html
Swift社区1 小时前
Flutter 页面为什么会频繁 rebuild?如何定位和优化?
flutter·前端框架·node.js
指尖跳动的光1 小时前
Vue的nextTick()方法
前端·javascript·vue.js
Joyee6913 小时前
RN 的初版架构——运行时异常与异常捕获处理
react native·前端框架
实习生小黄3 小时前
vue3静态文件打包404解决方案
前端·vue.js·vite
OpenTiny社区4 小时前
这是OpenTiny与开发者一起写下的2025答卷!
前端·javascript·vue.js
有意义4 小时前
从重复计算到无效渲染:用对 useMemo 和 useCallback 提升 React 性能
react.js·面试·前端框架
哟哟耶耶5 小时前
Plugin-安装Vue.js devtools6.6.3扩展(组件层级可视化)
前端·javascript·vue.js
计算机学姐5 小时前
基于SpringBoot的美妆销售系统【个性化推荐算法+数据可视化统计+库存预警+物流信息】
java·vue.js·spring boot·后端·mysql·信息可视化·mybatis
Knight_AL5 小时前
Vue + Spring Boot 项目统一添加 `/wvp` 访问前缀实践
前端·vue.js·spring boot