权衡的艺术

大约 5 分钟Vue读书笔记

当设计一个框架时,其各模块间是相互关联和制约的。框架设计者需对框架的定位和方向拥有全局的把控,以便后续的模块设计和拆分。而作为学习者,学习框架时需要全局认知,否则会被细节困住。

设计框架时需要考虑的多个方面,包括范式的选择(命令式或声明式)、运行时和编译时的选择(纯运行时、纯编译时或运行时+编译时)以及权衡不同选择带来的优缺点。

命令式和声明式

从范式上看,视图层框架通常分为命令式和声明式,它们各有优缺点。框架设计者应该了解两种范式并尝试将它们结合起来,以做出正确的选择。

命令式框架一大特点就是关注过程(如 jQuery 是典型的命令式框架)。即自然语言描述能够与代码产生一一对应的关系,代码本身描述的是“做事的过程”,符合逻辑直觉。

声明式框架更加关注结果。如使用 Vue.js 实现功能,仅需要声明一个“结果”,至于其内部实现过程,则由 Vue.js 完成(即 Vue.js 封装了过程,其内部实现是命令式的,以更声明式暴露给用户)。

性能与可维护性的权衡

结论:声明式代码的性能不如命令式代码

命令式代码通常更容易进行性能优化,因为可以直接控制每一个操作的执行顺序和细节。而声明式代码不一定能做到,因为它描述的是结果。

框架为了实现最优的更新性能,它需要找到前后的差异并只更新变化的地方

公式:声明式代码的更新性能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗

声明式代码会比命令式代码多出找出差异的性能消耗,当找出差异的性能消耗为0时(最理想时),两者的性能相同,但无法超越,毕竟框架封装了命令式代码,就是为了提供面向用户的声明式接口

Vue.js 选择声明式的设计方案,是因为声明式代码的可维护性更强,更直观。

框架设计者需要在保持可维护性的前提下最小化性能损失,因为采用声明式代码可能会导致一定的性能损失。

虚拟DOM的性能如何

最小化找出差异的性能消耗可以让声明式代码性能无限接近命令式代码。

而虚拟DOM,旨在最小化查找差异所带来的性能消耗。

编写绝对优化的命令式代码,代价高,但投入产出比可能并不高。

虚拟DOM的目的:在使用声明式代码的情况下最大限度地接近命令式代码的性能,以确保应用程序的性能不会太差。

使用 innerHTML 和虚拟DOM在创建页面时的性能差距不大,两者都需要新建所有 DOM 元素。

使用 innerHTML 会销毁所有旧的 DOM 元素,再全量创建新的 DOM 元素。虚拟 DOM 会比较新旧虚拟 DOM 找到变化的元素并更新它。

即,虚拟 DOM 在更新页面时只会更新必要元素,但 innerHTML 需全量更新。

虚拟DOM 和 innerHTML 在更新页面时的性能:

虚拟DOMinnerHTML
纯JavaScript运算创建新的 JavaScript 对象 + Diff渲染 HTML 字符串
DOM 运算必要的 DOM 更新销毁所有旧DOM
新建所有新 DOM
性能因素与数据变化量相关与模板大小相关

虚拟DOM 是声明式的,心智负担小,可维护性强。

运行时和编译时

设计框架时有三种选择:纯运行时运行时 + 编译时纯编译时

纯运行时框架在浏览器中动态地生成和处理应用程序的代码。例如,通过一个 Render 函数,提供描述树型结构的数据对象,然后递归地将数据渲染成 DOM 元素。

手写树型结构麻烦且不直观,可引入编译手段,把 HTML 标签编译成树型结构的数据对象。例如,写一个 Compiler 程序编译 HTML 字符串,此时就变成了运行时 + 编译时框架。代码运行的时候才开始编译,会产生一定性能开销。也可以在构建时执行 Compiler 程序将用户提供内容编译好,运行时无须编译,对性能很友好。

纯编译时框架则连 Render 都不需要,只需要 Compiler 函数即可。不支持任何运行时内容,代码需通过编译器编译后才能运行。

纯运行时没办法分析用户提供内容,加入编译步骤则可以分析用户提供内容并进行优化,纯编译时性能可能更好,但有损灵活性。

Vue.js 3 是一个运行时 + 编译时的框架,它在保持灵活性基础上,还能通过编译手段分析用户提供的内容,以进一步提升更新性能。