第一章中从多个角度讲述了 Vue.js
设计时的权衡:
命令式和声明式
命令式关注过程,声明式强调结果。 书中举了一个例子: 获取一个 id 为 app 的 div 标签,将它的文本内容设置为 hello world
,并给他绑定一个点击会弹出 ok 的事件。如果我们通过原生实现:
js
const div = document.querySelector('#app'); // 获取指定 div
div.innerText = 'hello world'; // 设置文本内容
div.addEventListener('click', () => { alert('ok') }); // 绑定点击事件
很显然这是一个命令式的操作,而在声明式中,我们直接将这一需求声明给 Vue.js
,无需自己去考虑每一步如何操作DOM:
js
<div @click="() => { alert('ok') }">hello world</div>
考虑到最终的结果是改变了DOM的,必然是执行了JavaScript代码的,所以 Vue.js
的内部实现仍然是命令式 的,只是暴露给用户的部分采用了声明式。
性能与可维护性
对于上面的代码,假如我们想把文本内容修改成 "hello vue":
js
div.innerText = 'hello vue'; // 原生命令式
<div @click="() => { alert('ok') }">hello vue</div> // Vue.js 声明式
在声明式中,框架并不知道哪些地方发生了变更,它需要找到前后的差异再对变化进行更新,而它执行更新的代码仍然是:div.innerText = 'hello vue'
。
- 命令式代码的更新性能消耗 = 直接修改的性能消耗
- 命令式代码的更新性能消耗 = 直接修改的性能消耗 + 找出差异的性能消耗
不难意识到一个结论,声明式代码的性能是不会优于命令式代码的性能的。而命令式的缺点就在于可维护性,从上面的代码也可以感受到,同样的功能 Vue.js
一行实现,而命令式需要三行;声明式的代码非常直观,利于维护。
虚拟 DOM 的性能到底如何
虚拟 DOM 就是为了最小化上面提到的找出差异的性能消耗而出现的。
书中对比了虚拟 DOM 和原生或 jQuery 开发中常用到的模板字符串配合 innerHTML
方式在更新页面时的性能:
首先我们要明确一些前提:涉及 DOM 的计算要远比 JavaScript 层面的计算性能差,有数量级的差距。Diff 的性能细究起来可能比渲染模板字符串要差,但是仍在同一个数量级。然后再来看二者在创建页面与更新页面时的性能对比:
创建页面时:
innerHTML(模板) | 虚拟DOM | |
---|---|---|
JavaScript 层面的计算 | 渲染模板 HTML 字符串 | 创建 JavaScript 对象(VNode) |
DOM 层面的计算 | 新建所有 DOM 元素 | 新建所有 DOM 元素 |
更新页面时:
innerHTML(模板) | 虚拟DOM | |
---|---|---|
JavaScript 层面的计算 | 渲染模板 HTML 字符串 | · 创建新的 JavaScript 对象 + Diff |
DOM 层面的计算 | · 销毁所有的旧 DOM · 新建所有的新 DOM · 与模板大小相关 | · 必要的 DOM 更新 · 与数据变化量相关 |
根据我们强调过的前提,二者在创建页面时性能相近;更新页面时,Diff 可以帮助我们只更新必要的元素,而 nnerHTML
要全量更新。一旦模板字符串中的 DOM 节点过多,全量更新和必要更新的性能差距就很大了。
运行时和编译时
概念:
- 运行时: 运行时框架是在我们将DOM的数据结构传入到渲染函数时,就可以直接渲染出真实的DOM结构元素。例如,我们可以创建一个render函数,通过给该函数传入源数据并调用,就可以得到真实的DOM结构。
- 编译时: 在编译时框架中,我们通常使用模板语法(如Vue的单文件组件)。这需要一个编译器来解析模板代码,将模板代码编译成真实的DOM结构。
- 运行+编译时: 这种框架既支持运行时(用户可以直接提供数据对象从而无须编译),又支持编译时(用户可以提供HTML字符串,我们将其编译为数据对象后再交给运行时处理)。
特点:
- 纯运行时 的框架:它没有编译的过程,因此我们没办法分析用户提供的内容 ,但是如果加入编译步骤,可能就大不一样了,我们可以分析用户提供的内容,看看哪些内容未来可能会改变,哪些内容永远不会改变,这样我们就可以在编译的时候提取这些信息,然后将其传递给
Render
函数,Render
函数得到这些信息之后,就可以做进一步的优化了。 - 纯编译时的框架:可以分析用户提供的内容。由于不需要任何运行时,而是直接编译成可执行的 JavaScript 代码,因此性能可能会更好,但是这种做法有损灵活性,即用户提供的内容必须编译后才能用。
Vue.js 3 在保留运行时的情况下,其性能甚至不输纯编译时的框架。