运行时和编译时的概念
-
vue中的html是真实的html么?
-
.vue文件是单文件组件, 在template中写入的html是真实的html标记节点么?
html<template> <div v-if='false'>hello</div> <m-com /> </template>
-
上述 v-if 和 m-com 自定义组件不是正常的html, 浏览器是不认识的,所以不会解析
-
上面两个两个问题的答案都是否定的
-
但是我们的浏览器正确渲染了结果,这里vue一定在中间做了处理
-
这个过程主要是:让假标记,被渲染成真实的html标签,最终呈现给用户
-
从这方面来说,Vue的核心功能,分为三项:
- 运行时:runtime
- 编译时:compiler
- 运行时 + 编译时: runtime + compiler
-
vue的响应式系统是一个 运行时 + 编译时 结合的复合系统
-
这三个概念早就提出过,vue也是使用了这个方案
运行时
-
vuejs/core/tree/main/packages
- runtime-core
- 这里对外暴露出一个函数:render
- 用于编程式地创建虚拟DOM树的函数
- 作用是:代替template模板来完成 DOM 的渲染
- runtime-dom
- runtime-core
-
使用render函数渲染出一个div
html<script src='vue.js'></script> <div id="app"></div> <script> const { render, h } = Vue // 生成vnode const vnode = h('div', {class: 'test'}, 'hello render') // 找到div容器 const box = document.querySelector('#app') // 渲染 render(vnode, box) </script>
render函数内部如何实现
假如基于的一个json想要渲染出一个页面, 应该如何实现
json如下
js
{
type: 'div',
props: {
class: test
},
children: 'hello render'
}
渲染出结果如下的div
html
<div class='test'>hello render</div>
原生js实现
js
const vnode = {
type: 'div',
props: {
class: 'test'
},
children: 'hello render'
}
function render(vnode) {
const ele = document.createElement(vnode.type)
ele.className = vnode.props.class
ele.innerText = vnode.children
document.body.appendChild(ele)
}
render(vnode)
- 以上的代码结构,就是一个运行时的代码框架
- 当然不考虑树的递归处理,仅仅处理这一个程序示例
- 简单总结:运行时,利用render把 vnode 渲染成真实 dom 节点
- 如果想通过 template 模板编写 html 的方式,就不是运行时的方式了
编译时
-
运行时做不到的事情,通过html标签结构的方式来渲染解析成真实的dom节点
-
vue中的编译时,准确来说是编译器的意思,核心代码在 compiler-core模块下
jsconst { compile, createApp } = Vue const html = ` <div class='test'>hello compiler</div> ` const app = createApp({ render: compile(html) }) app.mount('#app')
-
也就是把html字符串借助compile的方式变成render函数,再通过render函数渲染出真实dom
-
编译器的主要作用
- 把template的html编译成render函数,再利用 运行时
- 基于render 挂载对应的 dom
-
简单总结:编译时,把html的模板,编译成render函数
Vue为什么要设计成运行时和编译时这样
- 如果 compile 函数已经可以把 模板解析了,为何还要生成一个 render 函数
- 然后,再通过 render 函数进行挂载,这不多此一举?
- vue为何这样做?
1 )需要先知道dom渲染是如何进行的
可分为两部分
- 初次渲染,可以叫做挂载
- 更新渲染,叫做打补丁
初次渲染
容器盒子
html
<div id='app'></div>
需要渲染的节点
html
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
初次渲染,会生成一个ul标签,同时生成三个li标签,并把他们挂载到div中
更新渲染
此时ul里的内容发生了变化
html
<ul>
<li>3</li>
<li>1</li>
<li>2</li>
</ul>
li标签发生了顺序变化,期望浏览器如何更新渲染,有两种方案:
- a. 清空ul内容,重新渲染li节点
- 这种简单直接,但是操作执行dom次数多
- b. 删除原位置的 li-3,在新位置插入li-3
- 对比旧节点和新节点之间差异,根据差异,把原先最下面的删除,再新增一个新节点
- 这样,dom操作显著减少,js上的逻辑增多
- 但是哪种效率更高呢
2 )下面是对比测试
2.1 )1万个dom节点的生成
html
<body></body>
<script>
const len = 10000
console.time('element')
for (let i = 0; i < len; i++) {
const div = document.createElement('div')
document.body.appendChild(div)
}
console.timeEnd('element')
</script>
2.2 )1万个js对象的生成
html
<body></body>
<script>
const len = 10000
const divList = []
console.time('js')
for (let i = 0; i < len; i++) {
const json = { type: 'div' }
divList.push(json)
}
console.timeEnd('js')
</script>
- 耗时数据差距非常明显,我们可以看到js的计算性能远超过dom操作
- 这里, 即便是dom操作也有优化的空间, 比如通过DocumentFragment批量操作dom, 但是相对比之下 js 操作性能永远大于 dom 操作的性能
vue被设计成运行时和编译时框架
- 对于纯运行时,不存在编译器,每次渲染,我们需要提供一个复杂的js对象
- 对于纯编译时,因为缺少运行时计算,所以它只能把分析差异的操作,放到编译时进行,同样,因为省略了运行时,速度可能会更快,但是这种方式将会损失灵活性
- 可以查看官方文档,渲染函数 & JSX 和 渲染机制
- 再比如,svelte, 是一个纯编译时框架,但是它实际运行速度达不到理论上的速度
- 运行时 + 编译时:vue和react都是通过这种方式构建的,使其保持灵活性的基础上,尽量的进行性能的优化,达到一种平衡
副作用
- 当对数据进行 setter 或 getter 操作时,所产生一系列的后果
- 副作用可能会有多个
1 ) setter
-
赋值操作,比如执行下面代码
jsmsg = 'hello world'
-
这时 msg 就触发了一次 setter 的行为
-
假如说, msg 是一个响应性数据,这样一次数据改变,就会影响到对应的 视图改变
-
我们就可以说, msg 的 setter 行为,触发了一次副作用,导致视图跟随发生了变化
2 ) getter
-
getter 所表示的是取值的操作,比如,我们执行如下代码时
jselement.innerText = msg
-
对于变量 msg 而言,触发了一次 getter 操作,这一次取值操作,同样会导致 element 的 innerText发生改变
-
所以,我们可以说:msg 的 getter 行为触发了一次副作用,导致 element 的 innerText 发生了变化
-
Vue的响应性的核心,就依赖这两个特性: getter和setter
3 ) 副作用会产生多个么
可以的
html
<template>
<div>
<p>{{ obj.name }}</p>
<p>{{ obj.age }}</p>
</div>
</template>
<script>
const obj = ref({
name: '张三',
age: 30
})
obj.value = {
name: '李四',
age: 18
}
</script>
一次setter操作,会导致两个p标签内容变化,这就是多个副作用的体现
vue3设计核心
- vue3的核心可分为三大模块
- 响应性:reactivity
- 运行时:runtime
- 编译器:compiler
基于以下代码示例来说明
html
<template>
<div> {{ proxyTarget.name }} </div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const target = {
name: '张三'
}
const proxyTarget = reactive(target)
return {
proxyTarget
}
}
}
</script>
- 首先对于 响应性 reactivity 而言,对外暴露出 reactive 方法
- 它可以接收一个复杂数据类型参数(被代理对象),返回一个 proxy 对象,也就是代理对象
- 当 这个代理对象 proxyTarget 触发 getter 和 setter 行为时,都会产生对应的副作用
- 对于 编译器 compiler 而言,将模板转化为 render 函数
- 会把 template 里的div进行解析,解析出的内容变为 render 函数
- 最后,运行时 runtime 会把compiler返回的 render 函数进行渲染,从而得出真实dom