本文经过一个示例,描述了vue的编译及运行过程。例子:html
<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 => compilevue
最终生成的render函数以下:node
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渲染为真实的DOMexpress
当点击元素或者触发输入框改变时,触发派发更新,会再次调用updateComponent并从新生成新的vnode。例如点击元素,增长list长度时segmentfault
再次调用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时触发事件。以下app
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 }