vue實現梳理

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

update

當點擊元素或者觸發輸入框改變時,觸發派發更新,會再次調用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
}
相關文章
相關標籤/搜索