# 命令式和声明式
命令式编程 | 声明式编程 | |
---|---|---|
描述方式 | 详细描述解决问题的步骤和过程。强调“如何做” | 关注描述问题的目标,而不是实现的具体步骤。强调“是什么” |
代码风格 | 倾向于详细和具体的指令 | 倾向于简洁和抽象,隐藏底层细节 |
控制程度 | 提供更高的灵活性,可以直接控制每个步骤的执行 | 控制性较弱,不精确控制每个步骤,但能够更直观地表达目标 |
可读性 | 相对复杂,需要理解每一步的执行 | 简洁直观,易于理解 |
抽象层次 | 较低,直接操作底层资源(如DOM元素) | 较高,隐藏了底层实现的细节 |
应用场景 | 需要细粒度控制时(如底层DOM操作) | 适用于描述性任务,如UI布局、数据处理等 |
特点 | 通过一系列的指令来指导计算机如何完成任务 | 更关注于描述问题的目标,而不是实现的具体步骤 |
命令式:性能好。声明式:可维护性强。
虽然命令式代码在理论上比声明式代码性能更好,但是在实际的开发过程中,我们很难写出绝对优化的命令式代码;而声明式代码如果底层实现的好,其实最终性能是可能优于命令式代码的。
命令式:
声明式:
在使用 Vue 的过程中:
<div @click="() => alert('ok')">hello world</div>
可以看到,我们提供的是一个“结果”,至于如何实现这个“结果”,我们并不关心,这就像我们在告诉Vue.js:“嘿,Vue.js,看到没,我要的就是一个 div,文本内容是 hello world,它有个事件绑定,你帮我搞定吧。”至于实现该“结果”的过程,则是由 Vue.js帮我们完成的。
声明式
就是对底层复杂逻辑的封装,然后抛出一个接口给你使用,你不需要自己再去手动实现一遍这种复杂或者纯体力的劳动。只要声明了,就具备了此功能,不必专门为此做实现。
换句话说,Vue.js 帮我们封装了过程。因此,我们能够猜到 Vue.js 的内部实现一定是命令式的,而暴露给用户的却更加声明式。
# 性能与可维护性的权衡
理论上命令式代码可以做到极致的性能优化,因为我们明确知道哪些发生了变更,只做必要的修改就行了。但是声明式代码不一定能做到这一点,因为它需要找到前后的差异并只更新变化的地方。
虽然声明式代码的性能不优于命令式代码的性能(因为声明式代码会比命令式代码多出找出差异的性能消耗),但是声明式代码的可维护性更强,在采用命令式代码开发的时候,我们需要维护实现目标的整个过程,包括要手动完成 DOM 元素的创建、更新、删除等工作。而声明式代码展示的就是我们要的结果,看上去更加直观,至于做事儿的过程,并不需要我们关心,Vue.js都为我们封装好了。
而框架设计者要做的就是:在保持可维护性的同时让性能损失最小化。
声明式代码的更新性能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗,因此,如果我们能够最小化找出差异的性能消耗,就可以让声明式代码的性能无限接近命令式代码的性能。而所谓的虚拟 DOM,就是为了最小化找出差异这一步的性能消耗而出现的。
- 直观地对比 innerHTML 和虚拟 DOM 在创建页面时的性能:
可以看到,如果在同一个数量级,无论是纯 JavaScript 层面的计算,还是 DOM 层面的计算,其实两者差距不大。
- 再对比一下虚拟 DOM 和 innerHTML 在更新页面时的性能:
虚拟 DOM 在更新页面时只会更新必要的元素,但 innerHTML 需要全量更新。对于虚拟 DOM 来说,无论页面多大,都只会更新变化的内容,而对于 innerHTML 来说,页面越大,就意味着更新时的性能消耗越大。加上性能因素,那么最终它们在更新页面时的性能如图:
- 基于此,我们可以粗略地总结一下 innerHTML、虚拟 DOM 以及原生 JavaScript(指 createElement 等方法)在更新页面时的性能:
# 运行时和编译时
Vue.js 3仍然保持了运行时 + 编译时的架构,在保持灵活性的基础上能够尽可能地去优化。
纯运行时框架:无编译过程
源代码直接在运行时被解释或执行,无需预编译步骤。(代码运行的时候才开始编译)
利用 render 函数,直接把 虚拟
DOM
转化为 真实DOM
元素 的一种方式。在整个过程中,不包含编译的过程,所以无法分析用户提供的内容。纯编译时框架:有编译过程
源代码需要先编译成机器码或中间代码,然后才能执行。(不支持任何运行时内容,用户的代码通过编译器编译后才能运行。)
直接把
template
模板中的内容,转化为 真实DOM
元素。因为存在编译的过程,所以可以分析用户提供的内容。同时,没有运行时,理论上性能会更好,但是它的真实性能没有办法达到理论数据。两者都支持的框架:既有编译过程也有运行时处理
它的过程被分为两步:
- 先把
template
模板转化为render
函数。也就是 编译时- 再利用
render
函数,把 虚拟DOM
转化为 真实DOM
。也就是 运行时结合了编译时和运行时框架的优点,可以在编译时,分析用户提供的内容 在 运行时,提供足够的灵活性。这也是
vue
的主要实现方式。
- 首先,纯运行时的框架:由于它没有编译的过程,因此我们没办法分析用户提供的内容。
- 但是如果加入编译步骤,可能就大不一样了,我们可以分析用户提供的内容,看看哪些内容未来可能会改变,哪些内容永远不会改变,这样我们就可以在编译的时候提取这些信息,然后将其传递给 Render 函数,Render 函数得到这些信息之后,就可以做进一步的优化了。
- 然而,假如我们设计的框架是纯编译时的,那么它也可以分析用户提供的内容。由于不需要任何运行时,而是直接编译成可执行的 JavaScript 代码,因此性能可能会更好,但是这种做法有损灵活性,即用户提供的内容必须编译后才能用。
- 实际上,在这三个方向上业内都有探索,其中Svelte 就是纯编译时的框架,但是它的真实性能可能达不到理论高度。 Vue.js 3 在保留运行时的情况下,其性能甚至不输纯编译时的框架。
# 总结
- 命令式在理论上可以做到极致优化,但是用户要承受巨大的心智负担;而声明式能够有效减轻用户的心智负担,但是性能上有一定的牺牲,框架设计者要想办法尽量使性能损耗最小化。
- 声明式的更新性能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗。虚拟 DOM 的意义就在于使找出差异的性能消耗最小化。
- Vue.js 3 是一个编译时 + 运行时的框架,它在保持灵活性的基础上,还能够通过编译手段分析用户提供的内容,从而进一步提升更新性能。