vue中與事件相關的實例方法


上一篇文章介紹了Vue.js內部的整體結構,知道了它會向構造函數添加一些屬性和方法。

在Vue.js內部有這樣一段代碼

 

其中定義了Vue構造函數,然后分別調用initMiXin,stateMixin,eventsMixin,lifecycleMixin,renderMixin,並將Vue構造函數當作參數傳給這5個函數

這5個函數的作用就是向Vue的原型上掛載方法

 

 當函數initMixin被調用時,會向Vue構造函數的prototype屬性添加_init方法,執行new Vue()時,會調用_init方法。

 

好,接下來進入到本文的標題,與事件相關的實例方法有4個,分別是,vm.$on , vm.$emit,vm.$once,vm.$off;

這四個方法實在eventsMixin中掛在到Vue構造函數和prototype屬性中的。

當eventsMixin被調用的時候,會向Vue構造函數的prototype屬性添加4個實例方法。

 

下面將介紹一下這四種方法

1、vm.$on

vm.$on(event,callback)

  •  參數:
  • {string | Array<string>} event (數組只在 2.2.0+ 中支持)
  • {Function} callback

 用法:監聽當前實例上的自定義事件,事件可以由vm.$emit觸發,回調函數會接收所有傳入事件所觸發的函數的額外參數

 原理:只需要在注冊事件時將回調函數收集起來,在觸發事件時將收集起來的回調函數依次調用即可,代碼如下

Vue.prototype.$on = function (event, fn) {
    var vm = this;
    if (Array.isArray(event)) {
      for (var i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn);
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn);
    }
    return vm
  };
當event為數組的時候,需要遍歷數組,使得數組中的每一項都調用vm.$on,使回調可以注冊到數組中每項事件名所指定的事件列表中,當event參數不為數組時,就像事件列表中添加回調。
vm._events是一個對象,用來存儲事件,使用事件名(event)從vm._events中取出事件列表,如果列表不存在,則使用空數組初始化,然后將回調函數添加到事件列表中。
vm._events在執行new Vue()時,會執行this._init方法進行一系列操作,其中就在vue.js的實例上創建一個_events

  

2、vm.$emit

vm.$emit(eventName,[...args])

 參數:

  • {string} eventName
  • [...args]

 用法:觸發當前實例上的事件,附加參數都會傳給監聽器回調。

 原理:vm.$emit作用是觸發事件,所有的事件監聽器回調都會存儲在vm._events中,所以觸發事件的實現思路是使用事件名從vm._events中取出對應的事件監聽器回調

列表,然后依次執行列表中的監聽器回調並將參數傳遞給監聽器回調,代碼如下

Vue.prototype.$emit = function (event) {
    var vm = this;
    var cbs = vm._events[event];
    if (cbs) {
    //   const args = toArray(arguments,1);
      cbs = cbs.length > 1 ? toArray(cbs) : cbs;
      var args = toArray(arguments, 1);
      var info = "event handler for \"" + event + "\"";
      for (var i = 0, l = cbs.length; i < l; i++) {
          try{
              cbs[i].apply(vm,args)
          }catch(e){
              handleError(e,vm,`event handler for "${event}"`)
          }
      }
    }
    return vm
  };
使用event從vm._events中取出事件監聽器回調函數列表,並將其賦值給變量cbs,如果cbs存在,依次調用每一個監聽器回調並將其所有參數傳給監聽器回調。
toArray的作用是將類似於數組的數據換成真正的數組,它的第二個參數是起始位置,也就是說,args是一個數組,里面包含除第一個參數之外的所有參數。

  

 $on和$emit可以結合起來使用,大部分用於父子組件傳值,但是這里有一個問題

1、究竟是由子組件內部主動傳數據給父組件,由父組件監聽接收(由子組件中操作決定什么時候傳值)

2、還是通過父組件決定子組件什么時候傳值給父組件,然后再監聽接收 (由父組件中操作決定什么時候傳值)

兩種情況都有

 $meit事件觸發,通過子組件內部的事件觸發自定義事件$emit

 $meit事件觸發, 可以通過父組件操作子組件 (ref)的事件來觸發 自定義事件$emit

 

第一種情況

父組件   所以msg打印出來就是蠟筆小柯南

 

 子組件

 

第二種情況

父組件    通過ref操作子組件觸發事件

 

 子組件

 

 

 

2、vm.$off([event,callback])

參數:

  • {string | Array<string>} event (只在 2.2.2+ 支持數組)
  • {Function} [callback]

用法:

 移除自定義事件監聽器。

  • 如果沒有提供參數,則移除所有的事件監聽器;

  • 如果只提供了事件,則移除該事件所有的監聽器;

  • 如果同時提供了事件與回調,則只移除這個回調的監聽器。

Vue.prototype.$off = function(event,fn){
    const vm = this;
    //1-1、沒有提供參數的情況,此時需要移除所有事件的監聽器
    if(!arguments.length){
        // 當arguments.lengtha為0時,說明沒有任何參數,這時需要移除所有事件監聽器
        // 因此重置了vm._events屬性,vm._events存儲所有事件,所以將vm._events重置
        // 為初始狀態就等同於將所有事件都移除了。
        vm._events = Object.create(null);
        return vm;
    }
    // 1-2、vm.$off的第一個參數支持數組,當event為數組的時候,只需要將數組遍歷一遍,然后
    // 數組中的每一項依次調用vm.$off;
    if(Array.isArray(event)){
        for(let i = 0 ; i<event.length; i++){
            this.$off(event[i],fn)
        }
        return vm;
    }
    // 2、只提供了事件名,則移除該事件所有的監聽器,只需要將vm._events中的event重置為空就行
    const cbs = vm._events[event];
    if(!cbs){
        return vm;
    }
    // 這里做了一個安全檢測,如果這個事件沒有被監聽,vm._events內找不到任何監聽器,直接退出
    // 程序,然后判斷是否只有一個參數,如果是,將事件名在vm._events中所有事件都移除,只需要
    // 將vm._events上以該事件為屬性的值設置為null即可
    if(arguments.length === 1){
        vm._events[event] = null;
        return vm;
    }
    // 3、如果同時提供了事件與回調,那么只移除這個回調的監聽器,將使用參數中提供的事件名從
    // vm._events取出事件列表,然后從列表中找到與參數中提供的回調函數相同的那個函數,並
    // 將它從列表中移除
    if(fn){
        // 先判斷是否有fn參數,有則說明用戶同時提供了event和fn兩個參數,然后從vm._events
        // 中取出事件監聽器列表並遍歷它,如果列表中的某一項與fn相同,或者某一項的fn屬性與fn相同,
        // 使用splice方法將它從列表中移除,當循環結束后,列表中所有與用戶參數中提供的fn相同的監聽器
        // 都會被移除
        const cbd = vm._events[event];
        let cb;
        let i = cbs.length;
        while(i--){
            // 這里有一個細節要注意,在代碼中遍歷列表是從后向前循環,這樣在列表中移除當前
            // 位置的監聽器,不會影響列表中未遍歷到的監聽器位置,如果是從前向后遍歷,那么當從
            // 列表中移除一個監聽器時,后面的監聽器會自動向前移動一個位置,會導致下一輪循環
            // 時跳過一個元素。
            cb = cbs[i];
            if(cb === fn || cb.fn === fn){
                cbs.splice(i,1);
                break;
            }
        }
    }
    return vm;
}

 

2、vm.$once(event,callback)

參數:

  • {string} event
  • {Function} callback

用法:監聽一個自定義事件,但是只觸發一次。一旦觸發之后,監聽器就會被移除

Vue.prototype.$once = function (event, fn) {
      var vm = this;
      function on () {
        vm.$off(event, on);
        fn.apply(vm, arguments);
      }
      on.fn = fn;
      vm.$on(event, on);
      return vm
    };
在vm.$once中調用vm.$on來實現監聽自定義事件的功能,當自定義事件觸發后會執行攔截器,將監聽器從事件列表中移除。
在vm.$once使用vm.$on來監聽事件,首先,將函數on注冊到事件中,當自定義事件被觸發時,會執行函數on(在這個函數中,會用時vm.$off將自定義事件移除),然后
手動執行函數,並將參數arguments傳遞給函數fn,這樣就可以實現vm.$once的功能。
但是要注意on.fn = fn這行代碼,前面介紹vm.$off時提到,在移除監聽器時,需要將用戶提供的監聽器函數與列表中的監聽器函數進行對比,相同部分會被移除,這導致當我們使用攔截器代替監聽器注入到事件列表中時,
攔截器和用戶提供的函數時不相同的,此時用戶使用vm.$off來移除事件監聽器,移除操作失敗。
這個問題的解決方案是將用戶提供的原始監聽器保存到攔截器的fn屬性中,當vm.$off方法遍歷事件監聽器列表時,同時會檢查監聽器和監聽器的fn屬性是否與用戶提供的監聽器函數相同,只要有一個相同,就說明需要被移除
的監聽器找到了,將被找到的攔截器從監聽器列表中移除

  

以上就是與vue中與實例相關的方法的總結。有不當的地方還請包含。日后一定改進。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM