摘要:組件將回調函數保存在中,對同一可以綁定多個回調函數,同時,通過更新所有父組件的。這里的特殊處理暫且忽略,還得從其他源碼推敲用于調用自身對綁定的回調函數。
近期開發的項目中前端使用的是Vue框架,很輕量,也很好用。不過,因為用的是別人家開發的框架,代碼執行的情況是否跟我們意料的一致值得思考。調試代碼或者利用測試框架測試input/ouput挺好,不過我更傾向于看源碼。能夠被大眾所廣泛使用的框架的源碼非常值得一看,好處就不多說了,因人而異。
這次我看的是vue源碼里的eventsAPI部分,包括$emit/$broadcast/$dispatch等。
注:由于目前看到的只是冰山一角,所以牽連到其他部分的語句會暫時忽略,所以也有可能理解起來會有斷章取義的可能,如果有理解錯的還望指出,互相學習。在后續的源碼閱讀中,一有新的認識會立即更新。
eventsAPI源碼位置:src/instance/api/events.js
私有函數 modifyListenerCountvar hookRE = /^hook:/ function modifyListenerCount (vm, event, count) { var parent = vm.$parent // hooks do not get broadcasted so no need // to do bookkeeping for them if (!parent || !count || hookRE.test(event)) return while (parent) { parent._eventsCount[event] = (parent._eventsCount[event] || 0) + count parent = parent.$parent } }
在events.js里邊多次調用到該函數,用于向上遍歷父組件,更新事件計數器。
組件的_events屬性,記錄著每個event綁定的回調函數(數組),比如_events[event] = [func1, func2, ...].
組件的_eventsCount屬性,記錄著自己以及子組件對每個event綁定的回調函數的總數目。每當子組件對event事件綁定了n個回調,那父組件(一直向上遍歷到根)的_eventsCount[event]會+n。目前發現,_eventsCount在$broadcast會使用到。
Vue.prototype.$onVue.prototype.$on = function (event, fn) { (this._events[event] || (this._events[event] = [])) .push(fn) modifyListenerCount(this, event, 1) return this }
基礎函數,事件監聽綁定。組件將回調函數fn保存在_events[event]中,對同一event可以綁定多個回調函數,同時,通過modifyListenerCount更新所有父組件的_eventsCount[event]。
Vue.prototype.$onceVue.prototype.$once = function (event, fn) { var self = this function on () { self.$off(event, on) fn.apply(this, arguments) } on.fn = fn this.$on(event, on) return this }
$once:當event事件發生時,fn只會被調用一次,調用完成后通過$off解除綁定。
Vue.prototype.$offVue.prototype.$off = function (event, fn) { var cbs // all if (!arguments.length) { if (this.$parent) { for (event in this._events) { cbs = this._events[event] if (cbs) { modifyListenerCount(this, event, -cbs.length) } } } this._events = {} return this } // specific event cbs = this._events[event] if (!cbs) { return this } if (arguments.length === 1) { modifyListenerCount(this, event, -cbs.length) this._events[event] = null return this } // specific handler var cb var i = cbs.length while (i--) { cb = cbs[i] if (cb === fn || cb.fn === fn) { modifyListenerCount(this, event, -1) cbs.splice(i, 1) break } } return this }
$off:解除事件綁定,源碼可以看出它的三個調用方式:
vm.$off()
不帶參數:將刪除組件所有綁定的事件(this._events = {}),在此之前,會遍歷更新父組件的計數器。
vm.$off(event)
只帶參數event:將刪除組件對event綁定的所有事件,同樣會遍歷更新父組件的計數器。
vm.$off(event, fn)
帶齊參數event和fn:將刪除組件對event事件綁定的fn回調,同樣會遍歷更新父組件的計數器。
Vue.prototype.$emit = function (event) { var isSource = typeof event === "string" event = isSource ? event : event.name var cbs = this._events[event] var shouldPropagate = isSource || !cbs if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs // 這里的特殊處理暫且忽略,還得從其他源碼推敲 // this is a somewhat hacky solution to the question raised // in #2102: for an inline component listener like, // the propagation handling is somewhat broken. Therefore we // need to treat these inline callbacks differently. var hasParentCbs = isSource && cbs.some(function (cb) { return cb._fromParent }) if (hasParentCbs) { shouldPropagate = false } var args = toArray(arguments, 1) for (var i = 0, l = cbs.length; i < l; i++) { var cb = cbs[i] var res = cb.apply(this, args) if (res === true && (!hasParentCbs || cb._fromParent)) { shouldPropagate = true } } } return shouldPropagate }
$emit:用于調用自身對event綁定的回調函數。該函數會被$broadcast和$dispatch調用,所以對參數的event進行了適配。部分變量備注:
isSource:是否是源組件發出的$emit事件。也就是說,只有直接調用vm.$emit事件或者$dispatch率先觸發自己綁定的回調($dispatch源碼第一行)的時候,參數是event字符串,此時isScource才為true。其他情況,如$broadcast內部調用$emit,其參數會是一個非字符串,在下面的$broadcast和$dispatch可以看到,此時的參數會是{ name: event, source: this }。
event:由isSource可以得到:event即事件(字符串)。
shouldPropagate:是否需要繼續傳播事件觸發。源碼中,遍歷了event綁定的事件,除開(!hasParentCbs || cb._fromParent)這個不說,只要執行的綁定事件明確return true,shouldPropagate才會置為true。對于$progress,如果shouldPropagate為true,會觸發繼續向下傳播事件。
Vue.prototype.$broadcastVue.prototype.$broadcast = function (event) { var isSource = typeof event === "string" event = isSource ? event : event.name // if no child has registered for this event, // then there"s no need to broadcast. if (!this._eventsCount[event]) return var children = this.$children var args = toArray(arguments) if (isSource) { // use object event to indicate non-source emit // on children args[0] = { name: event, source: this } } for (var i = 0, l = children.length; i < l; i++) { var child = children[i] var shouldPropagate = child.$emit.apply(child, args) if (shouldPropagate) { child.$broadcast.apply(child, args) } } return this }
此處isSource的理解跟$emit的理解差不多,指代是否最開始調用$broadcast。
這里vm._eventsCount[event]起到作用了,如果該計數為0,說明其所有子組件包括遞歸下去的子組件都沒有對event綁定回調。
從for循環的寫法可以看出,這里何時停止事件傳播使用的方法類似于深度優先搜索(DFS)如下圖
A組件發出$broadcast,自身不會調用監聽event的事件,而是傳遞給子組件,子組件B1率先執行監聽event的事件,其中有一個綁定事件return true,那么該B1繼續傳播事件,C1率先執行,C1所有監聽event的回調事件都沒有return true,所以C1不會往它的子組件傳播事件。
到此,只是遍歷完最左側的線,接下來輪到C2執行,C2執行后再決定是否需要傳遞給其子組件,接下來C3....執行完B1的子組件,接下來就B2,然后...
從這里可以看出,如果某一層一個組件return true,那么會繼續遍歷新一層子組件,有點雪崩式的爆發,return true或許會導致性能下降,這種事件通知的機制或許需要改善改善,因為假設我只要通知B1和C1,結果還是會遍歷B層其他組件還有C層其他組件,這樣會消耗多余的資源,且注意,這里是同步。
Vue.prototype.$dispatchVue.prototype.$dispatch = function (event) { var shouldPropagate = this.$emit.apply(this, arguments) if (!shouldPropagate) return var parent = this.$parent var args = toArray(arguments) // use object event to indicate non-source emit // on parents args[0] = { name: event, source: this } while (parent) { shouldPropagate = parent.$emit.apply(parent, args) parent = shouldPropagate ? parent.$parent : null } return this }
$dispatch相對簡單,先觸發自身對event綁定的回調,如果自己沒有監聽event的回調,則會繼續調用父組件觸發相應綁定的事件。如果有回調,還需要判斷_fromParent這個屬性,這個不知何物,待發掘。
假設A->B->C三層,B發出$dispatch("e"),想要B和A執行,那么B需要return true; C發出$dispatch("e"),想要C和B執行,那么C需要return true。但此時B也return true了,所以A也會觸發。所以如果遇到這種情況,可以修改dispatch的事件名字,比如C換成$dispatch("f");或者通過傳遞其他參數來判斷是否需要return true。(推薦前者,比較干凈)
總結Vue的eventsAPI是比較好理解的模塊,在看源碼以前,原以為$broadcast和$dispatch是在$nextTick實現,現在才意識到是一調用便執行。所以如果有多個地方會return true,還是需要考慮下用其他方法,不然會阻塞挺久的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90970.html
摘要:目標是為了可以調試版本的,也就是下的源碼,所以主要是的開啟。結語至此就可以開心的研究源碼啦。文章鏈接源碼分析系列源碼分析系列之入口文件分析源碼分析系列之響應式數據一源碼分析系列之響應式數據二 概述 為了探究vue的本質,所以想debug一下源碼,但是怎么開始是個問題,于是有了這樣一篇記錄。目標是為了可以調試es6版本的,也就是src下的源碼,所以主要是sourceMap的開啟。原文來自...
摘要:至此算是找到了源碼位置。至此進入過渡的部分完畢。在動畫結束后,調用了由組件生命周期傳入的方法,把這個元素的副本移出了文檔流。這篇并沒有去分析相關的內容,推薦一篇講非常不錯的文章,對構造函數如何來的感興趣的同學可以看這里 Vue transition源碼分析 本來打算自己造一個transition的輪子,所以決定先看看源碼,理清思路。Vue的transition組件提供了一系列鉤子函數,...
摘要:本次分析的版本是。持續更新中。。。目錄的引入的實例化的引入這一章將會分析用戶在引入后,框架做的初始化工作創建這個類,并往類上添加類屬性類方法和實例屬性實例方法。 背景 Vue.js是現在國內比較火的前端框架,希望通過接下來的一系列文章,能夠幫助大家更好的了解Vue.js的實現原理。本次分析的版本是Vue.js2.5.16。(持續更新中。。。) 目錄 Vue.js的引入 Vue的實例化...
摘要:數據驅動一個核心思想是數據驅動。發生了什么從入口代碼開始分析,我們先來分析背后發生了哪些事情。函數最后判斷為根節點的時候設置為,表示這個實例已經掛載了,同時執行鉤子函數。這里注意表示實例的父虛擬,所以它為則表示當前是根的實例。 數據驅動 Vue.js 一個核心思想是數據驅動。所謂數據驅動,是指視圖是由數據驅動生成的,我們對視圖的修改,不會直接操作 DOM,而是通過修改數據。它相比我們傳...
閱讀 3832·2021-09-29 09:34
閱讀 3778·2021-09-27 13:34
閱讀 572·2021-09-24 09:47
閱讀 3040·2019-08-30 15:53
閱讀 1814·2019-08-26 13:54
閱讀 2090·2019-08-26 13:43
閱讀 541·2019-08-23 14:47
閱讀 1747·2019-08-23 14:28