本文經過一個示例,描述了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 }