框架设计概览#权衡的艺术
- 作为框架设计者,一定要对框架的定位和方向拥有全局的把控,这样才能做好后续的模块设计和拆分。
- 作为学习者,在学习框架的时候,也应该从全局的角度对框架的设计拥有清晰的认知,否则很容易被细节困住,看不清全貌。
一、权衡的艺术:命令式和声明式
- 命令式框架:jQuery
关注过程:自然语言描述能够与代码产生一一对应的关系,代码本身描述的是“做事的过程”,这符合我们的逻辑直觉。
$('#app) // 获取div
.text('hello world') // 设置文本
.on('click', () => { // 绑定点击事件
alert('ok') // 弹出提示
})
- 声明式框架:Vue.js
关注结果:我们提供以一个“结果”,至于如何实现这个“结果”,我们并不关心。Vue.js帮我们封装了过程。
<div @click="() => alert('ok')">hello world</div>
Vue.js的内部实现是命令式,而暴露给用户的却更加声明式。
范式 | 特点 | 优点 |
---|---|---|
命令式 | 关注过程 | 性能好 |
声明式 | 关注结果 | 可维护性强 |
二、权衡的艺术:性能与可维护性的权衡
声明式代码的性能不优于命令式代码的性能。
框架本身就是封装了命令式代码才实现了面向用户的声明式。
理论上,命令式代码可以做到极致的性能优化,因为明确知道哪些发生了变更,只做必要的修改就行了。
对于框架来说,为了实现最优的更新性能,需要找到前后的差异并只更新变化的地方,然后再执行更新操作。
如果把直接修改的性能消耗定义为A,把找出差异的性能消耗定义为B,那么有:
- 命令式代码的更新性能消耗 = A
- 声明式代码的更新性能消耗 = B + A 声明式代码会比命令式代码多出找出差异的性能消耗。
在保持可维护性的同时,让性能损失最小化。
三、权衡的艺术:虚拟DOM的性能到底如何
虚拟DOM,为了最小化找出差异这一步的性能消耗而出现的:写声明式代码,保证应用程序的性能下限。
1. 新建页面
- innerHTML为了渲染出页面,首先要把字符串解析成DOM树 —— 涉及DOM的运算要比JavaScript层面的计算性能差
通过innerHTML创建页面的性能 = HTML字符串拼接的计算量 + innerHTML的DOM计算量。
- 虚拟DOM创建页面的过程分为2步:第一步是创建JavaScript对象 —— 真实DOM的描述;第二步是递归遍历虚拟DOM树,并创建真实DOM。
通过虚拟DOM创建页面的性能 = 创建JavaScript对象的计算量 + 创建真实DOM的计算量。
性能影响因素 | 虚拟DOM | innerHTML |
---|---|---|
纯JavaScript运算 | 创建JavaScript对象(VNode) | 渲染HTML字符串 |
DOM运算 | 新建所有DOM元素 | 新建所有DOM元素 |
宏观上,在同一个数量级,在创建页面的时候,都需要新建所有DOM元素 —— 两者性能没有差异。
2. 更新页面
- 使用innerHTML更新页面的过程是:重新构建HTML字符串,再重新设置DOM元素的innerHTML属性。而重新设置innerHTML属性就等价于销毁所有旧的DOM元素,再全量创建新的DOM元素。
- 使用虚拟DOM更新页面的过程是:重新创建JavaScript对象(虚拟DOM树 —— VNode),然后比较新旧虚拟DOM,找到变化的元素并更新它。
性能影响因素 | 虚拟DOM | innerHTML |
---|---|---|
纯JavaScript运算 | 创建新的JavaScript对象(VNode)+ Diff | 渲染HTML字符串 |
DOM运算 | · 必要的DOM更新 | · 销毁所有旧DOM · 新建所有新DOM |
性能因素 | 与数据变化量有关 | 与模板大小有关 |
在更新页面时,虚拟DOM在JavaScript层面的运算要比创建页面时多出一个Diff的性能消耗,但毕竟是JavaScript层面的运算,不会产生数量级的差异。而在DOM层面的运算,只会更新必要的元素。但innerHTML需要全量更新。
宏观上,在同一个数量级,在更新页面的时候,虚拟DOM的性能优于innerHTML。
另外,当更新页面时,影响虚拟DOM的性能因素与影响innerHTML的性能因素不同。对于虚拟DOM来说,无论页面多大,都只会更新变化的内容;而对innerHTML来说,页面越大,更新时的性能消耗越大。
3. 小结
innerHTML、虚拟DOM以及原生JavaScript(createElement等方法)在更新页面时的性能对比,如图:
四、权衡的艺术:运行时和编译时
- 纯运行时,无法分析用户提供的内容。
- 运行时 + 编译时:Vue.js 3,保持灵活性的基础上尽可能去优化性能。
- 纯编译时:Svelte,直接编译成可执行的JavaScript代码,性能可能会更好,但有损灵活性 —— 用户提供的内容必须编译后才能用。
五、权衡的艺术:总结
性能分析是一个多因素权衡过程。