【Vue原理】Event - 源碼版 之 綁定組件自定義事件

寫文章不容易,點個讚唄兄弟 專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】node

若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧函數

【Vue原理】Event - 源碼版 之 綁定組件自定義事件 學習

組件自定義事件實際上是我最感興趣的,我當時花了好多時間去探索的哈哈哈,探索完了以後,發現很簡單的,能夠先看下白話版瞭解下this

【Vue原理】Event - 白話版 prototype

我當時腦海中就幾個問題,我很想弄懂啊3d

一、父給子綁定的事件,存放在父組件仍是子組件?

二、父給子綁定自定義事件,子組件爲何能夠觸發?

三、子組件觸發事件後,是怎麼調用綁定的 父組件的方法的?

看看當時作的筆記時間,已通過了很久了啊code

公衆號

筆記看着很混亂,因此下定決心寫文章,寫得詳詳細細的,然本身一眼就明白,並且怕之後本身忘記component

首先確定是爲本身服務的,只是順便分享給你們,能幫到別人少走彎路而已哈哈哈對象

好的,很少說了,立刻進入主題~~看完文章的歡迎到下面投票啊啊啊啊blog


怎麼解析

一樣,一個給組件綁定自定義事件的模板

公衆號

而後template 解析成下面的渲染函數 公衆號

渲染函數執行,生成這樣的組件外殼VNode 公衆號

還能夠打印組件實例看一下

公衆號

你能夠看到,綁定的自定義事件,存在了 組件外殼VNode的 componentOptions.listeners 中

等下!

渲染函數中,明明把事件解析放在 on 的啊,怎麼到 listeners了

這裏記錄一下哈

當 _c('test') 執行的時候,由於是組件,因此內部會特別調用 createComponent 去生成組件的VNode

而這個VNode 是外殼VNode

下面的源碼中能夠很清楚的看到

一、把 on 賦值給了 listeners

二、listeners 傳給了 VNode 構造函數,保存到了 vnode.componentOptions

function createComponent(
    Ctor, data, context, 
    children, tag
) {    

    var listeners = data.on;

    data.on = data.nativeOn;    

    var vnode = new VNode(

        tag, // 組件名字
        data, undefined, undefined, 
        undefined, context,    
        // 下面這個對象就存在 vnode.componentOptions
        {            
            listeners: listeners,
        }
    );    

    return vnode

}
function VNode(
    tag, data, children, text, 
    elm, context, componentOptions 

) {  
    this.componentOptions = componentOptions;

};

想了解多一點Vnode能夠看 VNode - 源碼版

想了解 component 流程的,能夠看 Component - 白話版

因此第一個問題獲得答案,父給子綁定的事件,存放在子組件中!


怎麼綁定

好的,模板上的事件已經被解析並保存好了

接下來,就輪到 事件的註冊 showtime

這個事情,發生在建立組件實例的時候

若是你要問,具體是怎麼到了建立實例這裏的話,你能夠看下面兩篇文章,不過看你有沒有這個耐心了(學習的事,能不要耐心嗎)

Component - 源碼版 之 建立組件VNode

Component - 源碼版 之 掛載組件DOM

要不就看白話版吧

Component - 白話版

一、初始化實例

組件實例初始化會調用 Vue.prototype._init 沒錯了

而後 _init 會調用一個 initEvents 的東東去進行初始化事件對象,以下

Vue.prototype._init = function(options) {  

    // 增長組件選項,詳情往下看
    initInternalComponent(vm,options);

    initEvents(vm);    

    ....處理選項數據

}

initEvents:沒錯就是我!

二、初始化事件對象

initEvent 這個函數作了什麼事情呢?

一、給實例上添加一個 _event 對象,用於保存自定義事件

二、獲取到 父組件給 子組件綁定的自定義事件(不懂就接着往下看)

三、調用 updateComponentListeners 開始註冊

function initEvents(vm) {

    vm._events = Object.create(null);    

    var listeners = vm.$options._parentListeners;    

    if (listeners) {

        updateComponentListeners(vm, listeners);
    }
}

有個疑惑

_parentListeners 保存的是什麼東西?父組件給子組件的事件?

百思不得其解,誒,看到_init 中有一句代碼調用initInternalComponent,也許就是這個函數搞的鬼,咱們來看下

三、初始化組件信息

function initInternalComponent(vm, options) { 

    .....保存節點等信息
    
    // _parentVnode 是外殼節點
    var parentVnode = options._parentVnode; 
    
    // 保存父組件給子組件關聯的數據
    var vcp= parentVnode.componentOptions;

    vm.$options._parentListeners =vcp.listeners;   

    ....保存渲染函數

}

這個函數的做用是初始化一些組件的信息,包括轉移一些數據

哦~~原來就是經過這個函數偷偷摸摸轉移了 listeners 到了 vm.$option._parentListeners

前面說過【父給子綁定的事件】解析存放在 外殼節點vnode.componentOptions.listeners 中(不明白能夠翻到上面的<怎麼解析>部分),而後轉移的就是這個 listeners

解決這個疑惑了,好的,咱們接着來走 initEvent 剩下的流程把~~

來看下 updateComponentListeners

四、註冊事件

function updateComponentListeners(
    vm, listeners, oldListeners

) {

    var name, cur, old;    

    for (name in listeners) {

        cur = listeners[name];

        old = oldListeners[name];   

        // 沒有舊事件,就直接添加新事件

        if (typeof old === "undefined") {
            vm.$on(name, cur);
        }      

        // 新事件和舊事件不同,替換舊事件
        else if (cur !== old) {
            on[name] = cur;
        }
    }    

    // 移除舊事件
    for (name in oldListeners) {        

        if (typeof listeners[name] === "undefined") {

            vm.$off(name, oldOn[name]);
        }
    }
}

就是從他這裏開始註冊事件的

彷佛沒什麼好說的,注意一點

綁定和解綁事件,是直接調用 Vue 的自定義事件方法 $on 和 $off ,你們是否是很熟悉???

沒錯,在這篇文章中說過

【Vue原理】Event - 源碼版 之 自定義事件

這就解釋咱們開篇第二個問題了!!!!

爲何我給子組件綁定自定義事件,能夠在子組件像下面這樣觸發?

this.$emit('test')

由於 組件綁定的自定義事件 和 Vue 的自定義事件

兩種事件都是使用同一種方法註冊的,因此都存在一樣一個事件對象 【vm._events】 中,是同樣操做的流程

就是這樣的

公衆號

能夠看到組件實例上的 _events

公衆號

而且知道了第三個問題,怎麼調用到父組件的方法?

由於給子組件註冊事件的時候,直接存放父組件的回調,因此觸發事件會調用到父組件的方法

而後你還能夠問出一個問題!

爲何子組件觸發事件以後,調用父組件的方法,而父組件的方法上下文對象仍是父組件

哈哈,由於 methods 方法已經使用 bind 綁定啦,上下文對象固定了爲父組件實例的,因此無論誰調用,怎麼調用,都是父組件

詳情能夠看這裏,

Methods - 源碼版

公衆號

相關文章
相關標籤/搜索