本文通过一个示例,描述了vue的编译及运行过程。例子:
<div id="app">
{{ message }}
<input type="text" v-model="message"/>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
<div @click="onClick">
点击
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!',
list: [1, 2, 3]
},
methods: {
onClick () {
this.list.push(2);
}
}
})
</script>
编译
如果是使用vue的compiler+runtime版本,首先在mount之前会有一个编译流程,会通过new Vue时传入的el或者template里面的元素编译为一个render函数。该步骤主要思路是将template视为一个字符串,通过正则表达式不断匹配tag和attr等,parse生成AST树,之后再将优化后转化为render函数。
Vue._init => Vue.$mount => compileToFunctions => compile
最终生成的render函数如下:
with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n "+_s(message)+"\n "),_c('input',{directives:[{name:"model",rawName:"v-model",value:(message),expression:"message"}],attrs:{"type":"text"},domProps:{"value":(message)},on:{"input":function($event){if($event.target.composing)return;message=$event.target.value}}}),_v(" "),_c('ul',_l((list),function(item){return _c('li',[_v(_s(item))])}),0),_v(" "),_c('div',{on:{"click":onClick}},[_v("\n 点击\n ")])])}
一般来说,v-model就是:value和@input="function($event){message=$event.target.value}"的语法糖。从编译结果看,input使用v-model时多了一句$event.target.composing的判断,这句判断的主要作用是使用中文输入法还没有确定输入时,不会更新DOM,具体见v-model指令到底做了什么
渲染
根据传入的data和render函数,生成对应的VNode Tree
这期间会有一次依赖收集过程,且v-if为false的元素不会出现在生成的VNode Tree中。最后调用patch,因为没有oldVnode,会直接将VNode渲染为真实的DOM
update
当点击元素或者触发输入框改变时,触发派发更新,会再次调用updateComponent并重新生成新的vnode。例如点击元素,增加list长度时
再次调用patch时,会对比新旧VNode并将差异应用到真实的DOM上。简单来讲,会对比新旧VNode同级的tag和key是否有相同的,如果不同,则使用removeChild/appendChild/createElement等原生DOM方法将旧的删除新的增加,如果相同,则元素可以复用,但依然会对比新旧VNode的attrs等,并使用原生removeAttribute/setAttribute等触发DOM更新。具体对比方法参考虚拟DOM
更新DOM用到了nextTick,将需要更新的watchers放到一个queue里面,nextTick时触发其更新。因为一个数据更新可以会触发一连串的数据改变,不能每次数据改变都重新修改DOM,而应该一次性的修改,所以这里使用nextTick异步更改DOM,这也是为什么想获取更新后的DOM需要使用nextTick的原因。
事件的实现
对于原生事件,vue使用的原生addEventListener添加事件。对于自定义事件,vue采用的是事件中心的设计模式,调用on时注册事件,调用emit时触发事件。如下
vm._events = Object.create(null)
// 依赖收集
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
// ...
(vm._events[event] || (vm._events[event] = [])).push(fn)
// ...
return vm
}
// 触发更新
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
// ...
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。