Vue3 源码:运行时与编译时的区别及设计原理
运行时(runtime)
什么是运行时
运行时可以利用 render 把 vnode 渲染成真实 dom 节点
简易代码演示
利用 vue 的 render 和 vnode 渲染一个 dom
<html>
<head>
<meta charset="UTF-8" />
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.45/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { render, h } = Vue;
const vnode = h('div', {}, 'content');
const container = document.getElementById('app');
render(vnode, container);
</script>
</body>
</html>
如上代码我们引入了 vue.js 并从 vue 中解构了 render 和 h 函数,通过 render 最终渲染出了 vnode 中的 content。那么这个 render 以及 h 函数是什么呢。
简易版 render 与 h 函数
如下代码,我们写了一个 render 函数,h 函数则被替换成了一个对象,由此可见,vue 中提供的 render 与 h 就是一个将内容渲染到元素中的一个方法。
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div id="app"></div>
<script>
const vnode = {
type: 'div',
props: {
class: 'test',
},
children: 'hello world',
};
const container = document.getElementById('app');
function render(vnode) {
const ele = document.createElement(vnode.type);
ele.className = vnode.props.class;
ele.innerText = vnode.children;
document.body.appendChild(ele);
}
render(vnode);
</script>
</body>
</html>
编译时(compiler)
由上述的运行时代码所见,无论是我们自己抒写 vnode 还是用 h 函数去写都是一个很繁琐的过程,那么有没有什么简单的方法,直接在 html 标签结构去抒写代码呢。
示例代码
<html>
<head>
<meta charset="UTF-8" />
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.45/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { compile, createApp } = Vue;
const html = `
<div class="test">hello world</div>
`;
const renderFn = compile(html);
console.log(renderFn);
const app = createApp({
render: renderFn,
});
app.mount('#app');
</script>
</body>
</html>
如上代码我们从 vue 中解构了 compile,createApp,我们定义了一个类似与 html 结构的 html 字符串,然后调用了 compile 函数去编译这个字符串。
renderFn 打印后的结果如下

可以看出来 renderFn 最后返回的也就是一个 render 函数,最终我们通过 createApp 创建实例,加入 render 方法,最终挂载在 #app 下渲染成功
所以对编译时可以理解为:可以把一个 html 的节点,编译成 render 函数。
同时我们可以看出,vue 是一个运行时+编译时框架,通过 compile 解析 html 模板,生成 render 函数, 然后通过 runtime 解析 render,从而挂载真实 dom
vue 为什么要设计成一个运行时+编译时的框架呢
dom 是如何渲染的
1.初次渲染:我们可以把它叫做挂载 2.更新渲染:我们可以把它叫做打补丁
初次渲染
当初始 div 的 innerHTML 为空时
我们在该 div 中渲染如下节点
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
这样的一次渲染就是初始渲染,在这样的一次渲染中,我们会生成一个 ul 标签,同时生成三个 li 标签,并且把他们挂载到 div 中。
更新渲染
如果此时 ul 的标签内容发生了变化
<ul>
<li>3</li>
<li>1</li>
<li>2</li>
</ul>
那么这样的一次更新, 浏览器如何去更新这次渲染呢?方法有两种
- 删除原有的所有节点,重新渲染新的节点
- 删除原位置的
li - 3,在新的位置插入li - 3
那么上述两种方式,哪一种方式更好呢?
- 对于第一种方式,不需要任何比对,只需要执行
6次(删除3次,重新渲染 3 次 )dom操作。 - 对于第二种方式,在逻辑上需要分成两个步骤
- 对比旧节点和新节点之间的差异
- 根据差异,删除一个旧节点,增加一个新节点
根据上述的分析,我们知道了
- 第一种方式涉及更多的
dom操作 - 第二种方式涉及到
js计算 + 少量的dom操作
那么两种方式到底那种更快呢
测试 dom 操作与 js 操作到底哪个快
示例代码
<html>
<body></body>
<script>
const length = 10000;
console.time('element');
for (let i = 0; i < length; i++) {
const div = document.createElement('div');
document.body.appendChild(div);
}
console.timeEnd('element');
console.time('js');
const divList = [];
for (let i = 0; i < length; i++) {
const ele = {
type: 'div',
};
divList.push(ele);
}
console.timeEnd('js');
</script>
</html>

如上述结果所示,js 操作比 dom 操作快了 10 倍左右的时间,所以可想而知,dom 的操作要比 js 的操作耗时多得多, 即:dom 操作比 js 更加耗费性能
比对
- 针对纯运行时,因为不存在编辑器,我们只能提供一个复杂的
js对象,开发者很难去一直写这种大量的js对象来去表示html - 针对纯编译时,因为缺少运行时,所以他只能把分析差异的操作放到编译时进行,同样因为省略了运行时,所以速度可能会更快,但这种方式将损失灵活性,如
sevlte框架,纯编译时框架 - 运行时+编译时,如
vue与react,使其保持灵活性的基础上,尽可能的优化性能

还没有留言,来发表第一条吧~