vue运行机制
vue运行机制
vue核心的执行过程主要分为这几个阶段:
1)模板编译:生成可复用的render函数
2)响应式:通过Object.definedProperty监听到对象属性的get和set,实现双向绑定
3)初始渲染:执行render函数,访问data中的值,会被get监听,调用patch方法生成vdom
4)数据改变:数据发生改变会触发set,会进行更新re-render,通过patch方法对新旧vnode对比,更新视图
Vue 如何解析模板
-
模板是什么
-
本质:模板就是字符串
-
与html格式很像,但是模板中是有逻辑的,可以嵌入JS变量,如v-if, v-for等
-
视图最终还是需要由模板生成 html 来显示
-
模板必须先要转换成JS代码
-
有逻辑(v-if, v-for),必须用JS才能实现(图灵完备)
-
转换为html渲染页面,必须用JS才能实现
-
因此,模板要转换成render函数
-
-
-
render函数
-
render函数包含了模板中所有的信息,返回 vnode,解决了模板中的逻辑(v-if, v-for)问题
-
如何找到最终生成的render函数
找到vue源码,src/compiler/codegen/index.js,generate函数的render返回值
-
-
render函数与vdom
-
模板生成 html:vm._c
-
vm._c 和 snabbdom 中的 h 函数的实现很像,都是传入标签,属性,子元素作为参数
-
Vue.js 的 vdom 实现借鉴了 snabbdom
-
updateComponent 中实现了 vdom 的 patch
-
页面首次渲染执行 updateComponent
-
data 中每次修改属性,都会执行 updateComponent
-
Vue运行机制
第一步:解析模板成render函数:
-
首先要知道render函数的生成是在打包的时候,为什么呢?webpack打包的时候我们使用到了vue-template-compiler这个loader,它的作用是将template编译成render函数,所以说编译是第一步;响应式监听是在代码执行的时候。
-
编译过程compile分为parse,optimize,generate三部分
-
parse:将template解析成抽象语法树
-
optimize:每次重新渲染中,DOM中有一部分是不需要改变的,我们称之为static sub-trees,这一部分可以分离出来存储成常量,然后re-render的过程中也不再去渲染它,以及后边的patch过程也不再管它,比如我们常见的html的header等基本不会改变。
-
generate:generate过程会根据ast生成虚拟dom树,即vnode(在代码中是${code}),另外还会生成optimize中的静态树。
function generate ( ast: ASTElement | void, options: CompilerOptions ): CodegenResult { const state = new CodegenState(options) const code = ast ? genElement(ast, state) : '_c("div")' return { render: `with(this){return ${code}}`, staticRenderFns: state.staticRenderFns } }
-
vue demo
<div id="app">
<p>普通属性:{{ message }}</p>
<p>{{msg()}}</p>
<p>{{ct}}</p>
<input v-model="message">
<div v-for="item in items">
{{ item.text }}
</div>
<button v-on:click="bindClick">点我抓同伟</button>
</div>
// js
new Vue({
el: '#app',
data: {
message: '以vue的名义',
items: [{
text: '达康书记'
}, {
text: '育良书记'
}]
},
methods: {
bindClick: function() {
this.message = '这就抓同伟去';
},
msg: function() {
return this.message + "这个方法每次都会执行";
}
},
computed: {
ct: function() {
return this.message + "计算属性并不会每次都执行";
}
}
})
对应的render函数
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
},
[_c('p', [_v("普通属性:" + _s(message))]), _v(" "), _c('p', [_v(_s(msg()))]), _v(" "), _c('p', [_v(_s(ct))]), _v(" "), _c('input', {
directives: [{
name: "model",
rawName: "v-model",
value: (message),
expression: "message"
}],
domProps: {
"value": (message)
},
on: {
"input": function($event) {
if ($event.target.composing) return;
message = $event.target.value
}
}
}), _v(" "), _l((items),
function(item) {
return _c('div', [_v("\n\t\t " + _s(item.text) + "\n\t ")])
}), _v(" "), _c('button', {
on: {
"click": bindClick
}
},
[_v("点我出奇迹抓同伟")])], 2)
}
-
模板中的data信息都变成了js变量
-
模板中的v-model,v-if,v-for变成了js中的逻辑
-
render函数返回虚拟树vnode,详情见 https://www.jianshu.com/p/fc0084c97e05
第二步:响应式开始监听
-
通过Object.definedProperty监听到对象属性的get和set,该过程封装在Observer中
-
将data的属性代理到vm上
第三步:首次渲染,显示页面,且绑定依赖
-
初次渲染,执行updateComponent(负责patch),执行vm._render()
-
执行render函数,会访问data中的数据,访问时会被响应式的get监听到,get中会收集依赖(watcher)添加到消息订阅器(Dep)中
-
执行updateComponent,会走到dom的patch方法中
-
patch会将vdom渲染成真实dom,初次渲染完成
-
疑问:为什么要监听get,而不是set?
-
因为data中的很多属性,有些被用到,有些没被用到
-
只有被用到的属性才会走get
-
如果没走get,那么set的时候也不用关心
-
这样可以避免不必要的渲染
-
第四步:data发生变化,会触发re-render
-
data发生改变,set监听到
-
set中执行updateComponent
-
updateComponent重新执行vm._render()
-
新生成的vnode和之前的vnode对比,通过patch进行对比
-
将差异渲染到html
参考:
https://www.cnblogs.com/dora-zc/p/11111813.html#vue-%E5%A6%82%E4%BD%95%E8%A7%A3%E6%9E%90%E6%A8%A1%E6%9D%BF
上一篇: 线程池的核心实现