权衡的艺术
框架设计里到处都体现了权衡的艺术
1.1 命令式和声明式
命令式关注过程:
const div = document.querySelector('#app');
div.innerText = 'hello world';
div.addEventListener('click', () => { ... });
声明式关注结果:
<div @click="() => { ... }">hello world</div>
1.2 性能与可维护性的权衡
声明式代码的性能不优于命令式代码
- 命令式代码的更新性能消耗 = 直接修改消耗的性能(需要用户自己找出差异的心智负担)
- 声明式代码的更新性能消耗 = 直接修改消耗的性能 + 找出差异的性能消耗
框架本身就是为了封装命令式代码才实现了面向用户的声明式. 目的是通过声明式提升可维护性的同时让性能损失最小化.
1.3 虚拟dom的性能到底如何
- innerHTML字符串模板: 将字符串解析成dom树(销毁所有旧dom并创建新dom)
- 虚拟dom: 通过js对象, 比较新旧差异(diff)并更新
- 原声js: 直接进行精准的dom操作
| innerHTML | 虚拟dom | 原声js |
|---|---|---|
| 心智负担中等 | 心智负担小 | 心智负担大 |
| 性能差 | 可维护性强 | 可维护性差 |
| 性能不错 | 性能高 |
1.4 运行时和编译时
- 运行时
通过const obj = { tag: 'div', children: [{ tag: 'span', children: 'hello world' }] } Render(obj, document.body)Render函数将数据直接渲染成dom元素, 没有其他额外的步骤. - 运行时+编译时
通过编译的过程, 分析用户提供的内容. 知道哪些内容可能会改变, 哪些永远不会改变. 将结果传递给// 运行时编译(JIT即时编译)/构建时编译(AOT预编译) const html = ` <div> <span>hello world</span> </div>` // 通过Compiler得到树形结构的数据对象 const obj = Compiler(html) Render(obj, document.body)Render函数, 可以做进一步的优化. - 编译时
⬇️编译(不支持任何运行时内容, 代码需要通过编译器编译才能运行)<div> <span>hello world</span> </div>`不需要任何运行时, 可以分析用户提供的内容直接编译成可执行的js代码, 因此性能可能更好但有损灵活. 用户提供的内容需经过编译才能用.const div = document.createElement('div') const span = document.createElement('span') span.innerText = 'hello world' div.appendChild(span) document.body.appendChild(div)
总结
- 命令式与声明式的差异. 命令式理论上可以做到极致优化, 但要承受巨大的心智负担; 而声明式能有效减轻心智负担,但性能上有一定的牺牲. 框架设计者要尽量使性能损耗最小化
- 虚拟dom的性能: 虚拟dom的意义在于找出差异的性能消耗最小化. 声明式的更新性能消耗=找出差异的性能消耗+直接修改的性能消耗
- vue是一个编译时+运行时的框架, 它在灵活性的基础上通过编译手段分析用户提供的内容, 从而进一步提升更新性能.
《vuejs设计与实现》 1.权衡的艺术