Vue 組件事件觸發(fā)和監(jiān)聽實現(xiàn)源碼解析
正文
通常我們在使用Vue的時候,會使用$emit和$on來觸發(fā)和監(jiān)聽事件,但是有沒有思考是如何實現(xiàn)的呢?
今天帶來的是一個微型的事件觸發(fā)的庫,借它們的源碼來簡單初步了解Vue的事件觸發(fā)和監(jiān)聽的實現(xiàn)。
mitt使用TypeScript編寫,tiny-emitter使用原生ES5編寫,兩者對比tiny-emitter功能稍微豐富一寫,所以直接看tiny-emitter就好了。
Vue 的事件觸發(fā)和監(jiān)聽
我沒有標題黨,先來看一下Vue的組件事件怎么使用的:
// 父組件
<template>
<div>
<child @my-event="handleMyEvent"></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: {
Child
},
methods: {
handleMyEvent (event) {
console.log(event)
}
}
}
</script>
// 子組件
<template>
<div>
<button @click="handleClick">click</button>
</div>
</template>
<script>
export default {
methods: {
handleClick () {
this.$emit('my-event', { a: 1 })
}
}
}
</script>
這個代碼大家都熟,簡化之后其實可以理解成這樣的:
// 父組件
import Child from './Child.js'
const child = new Child()
child.$on('my-event', (event) => {
console.log(event)
});
// 子組件
export default class Child extends Emitter {
constructor () {
super();
}
handleClick () {
this.$emit('my-event', { a: 1 })
}
}
這里可以看到子組件是繼承自Emitter,Emitter是一個事件觸發(fā)和監(jiān)聽的類,這個類的實現(xiàn)就是我們今天要學習的。
先來學習tiny-emitter的實現(xiàn),最后把Emitter這個類完成。
源碼分析
tiny-emitter的源碼很簡單,加上注釋和換行也就68行,先一睹為快:
function E () {
// Keep this empty so it's easier to inherit from
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
}
E.prototype = {
on: function (name, callback, ctx) {
var e = this.e || (this.e = {});
(e[name] || (e[name] = [])).push({
fn: callback,
ctx: ctx
});
return this;
},
once: function (name, callback, ctx) {
var self = this;
function listener () {
self.off(name, listener);
callback.apply(ctx, arguments);
};
listener._ = callback
return this.on(name, listener, ctx);
},
emit: function (name) {
var data = [].slice.call(arguments, 1);
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
var i = 0;
var len = evtArr.length;
for (i; i < len; i++) {
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
},
off: function (name, callback) {
var e = this.e || (this.e = {});
var evts = e[name];
var liveEvents = [];
if (evts && callback) {
for (var i = 0, len = evts.length; i < len; i++) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
liveEvents.push(evts[i]);
}
}
// Remove event from queue to prevent memory leak
// Suggested by https://github.com/lazd
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
};
module.exports = E;
module.exports.TinyEmitter = E;
一共只有4個方法,on、once、emit、off,下面一個一個開始分析。
on
on方法是用來監(jiān)聽事件的,代碼如下:
function on(name, callback, ctx) {
var e = this.e || (this.e = {});
(e[name] || (e[name] = [])).push({
fn: callback,
ctx: ctx
});
return this;
}
它接收三個參數:
name:事件名稱callback:事件觸發(fā)時的回調函數ctx:回調函數的上下文
它會判斷this.e是否存在,如果不存在就創(chuàng)建一個空對象;
this.e是用來存儲事件的,this指向就不多講了吧。
然后把name作為key,callback和ctx作為value,存到this.e中,用于后面的事件觸發(fā);
最后返回this,這樣就可以鏈式調用了。
once
once方法同樣是用來監(jiān)聽事件的,但是它只會觸發(fā)一次,代碼如下:
function once(name, callback, ctx) {
var self = this;
function listener() {
self.off(name, listener);
callback.apply(ctx, arguments);
};
listener._ = callback
return this.on(name, listener, ctx);
}
它同on方法一樣,接收三個參數,最后也是調用的on方法,但是它會在on方法的基礎上做一些處理。
它內部會定義一個listener函數,然后將這個函數作為on方法的回調函數,最后調用on方法。
listener函數會調用off方法,把自己從事件隊列中移除,然后再調用callback函數。
emit
emit方法是用來觸發(fā)事件的,代碼如下:
function emit(name) {
var data = [].slice.call(arguments, 1);
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
var i = 0;
var len = evtArr.length;
for (i; i < len; i++) {
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
}
它接收一個參數,就是事件名稱,然后把除了第一個參數以外的參數,轉成數組,存到data中。
然后從this.e中取出name對應的事件隊列,如果沒有就創(chuàng)建一個空數組,然后把這個數組復制一份,存到evtArr中。
這里老是對this.e做判斷好麻煩,其實可以在構造函數中初始化this.e,這樣就不用每次都判斷了。
然后遍歷evtArr,依次調用每個事件的回調函數,把data作為參數傳進去,最后返回this。
off
off方法是用來移除事件的,代碼如下:
function off(name, callback) {
var e = this.e || (this.e = {});
var evts = e[name];
var liveEvents = [];
if (evts && callback) {
for (var i = 0, len = evts.length; i < len; i++) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
liveEvents.push(evts[i]);
}
}
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
它接收兩個參數,name和callback,name是事件名稱,callback是回調函數。
它會從this.e中取出name對應的事件隊列,然后通過遍歷,把不等于callback的事件,存到liveEvents中。
最后判斷liveEvents是否為空,如果不為空,就把liveEvents賦值給e[name],否則就刪除e[name]。
動手實現(xiàn)
上面已經分析完tiny-emitter的源碼了,我們現(xiàn)在就來實現(xiàn)最開始提到的Emitter類。
當然這里還是簡化版,只有on和emit方法。
class Emitter {
constructor() {
this.events = {};
}
$on(name, callback) {
if (!this.events[name]) {
this.events[name] = [];
}
this.events[name].push(callback);
}
$emit(name, ...args) {
if (this.events[name]) {
this.events[name].forEach(callback => callback(...args));
}
}
}
這里使用ES6的class語法來實現(xiàn)這個功能;
constructor方法用來初始化events對象,這樣對比tiny-emitter的源碼,就不用每次都判斷this.e是否存在了;
$emit通過...args來接收除了第一個參數以外的參數,就可以不用使用arguments了;
整體來說使用ES6的class語法來實現(xiàn),代碼量更少,更加簡潔,來看了結果:

總結
通過學習tiny-emitter的源碼,我們學習到一個事件總線的設計思路以及實現(xiàn)方式。
了解事件總線的設計思路,可以幫助我們更好地理解Vue的事件總線,這可能對我們使用或者閱讀源碼有幫助。
最后通過實現(xiàn)一個簡單的事件總線,讓我們加深對這個事件總線的理解,也通過將這個實現(xiàn)轉換為ES6的class語法,讓我們對ES6的class語法有更深的理解。
以上就是Vue 組件事件觸發(fā)和監(jiān)聽實現(xiàn)源碼解析的詳細內容,更多關于Vue 組件事件觸發(fā)監(jiān)聽的資料請關注腳本之家其它相關文章!
相關文章
Vue實現(xiàn)一種簡單的無限循環(huán)滾動動畫的示例
這篇文章主要介紹了Vue實現(xiàn)一種簡單的無限循環(huán)滾動動畫的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-01-01
elementUI Pagination 分頁指定最大頁的問題及解決方法(page-count)
項目中遇到數據量大,查詢的字段多,但用戶主要使用的是最近的一些數據,1萬條以后的數據一般不使用,這篇文章主要介紹了elementUI Pagination 分頁指定最大頁的問題及解決方法(page-count),需要的朋友可以參考下2024-08-08

