摘要:組件將回調(diào)函數(shù)保存在中,對同一可以綁定多個回調(diào)函數(shù),同時,通過更新所有父組件的。這里的特殊處理暫且忽略,還得從其他源碼推敲用于調(diào)用自身對綁定的回調(diào)函數(shù)。
近期開發(fā)的項目中前端使用的是Vue框架,很輕量,也很好用。不過,因為用的是別人家開發(fā)的框架,代碼執(zhí)行的情況是否跟我們意料的一致值得思考。調(diào)試代碼或者利用測試框架測試input/ouput挺好,不過我更傾向于看源碼。能夠被大眾所廣泛使用的框架的源碼非常值得一看,好處就不多說了,因人而異。
這次我看的是vue源碼里的eventsAPI部分,包括$emit/$broadcast/$dispatch等。
注:由于目前看到的只是冰山一角,所以牽連到其他部分的語句會暫時忽略,所以也有可能理解起來會有斷章取義的可能,如果有理解錯的還望指出,互相學習。在后續(xù)的源碼閱讀中,一有新的認識會立即更新。
eventsAPI源碼位置:src/instance/api/events.js
私有函數(shù) 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里邊多次調(diào)用到該函數(shù),用于向上遍歷父組件,更新事件計數(shù)器。
組件的_events屬性,記錄著每個event綁定的回調(diào)函數(shù)(數(shù)組),比如_events[event] = [func1, func2, ...].
組件的_eventsCount屬性,記錄著自己以及子組件對每個event綁定的回調(diào)函數(shù)的總數(shù)目。每當子組件對event事件綁定了n個回調(diào),那父組件(一直向上遍歷到根)的_eventsCount[event]會+n。目前發(fā)現(xià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 }
基礎(chǔ)函數(shù),事件監(jiān)聽綁定。組件將回調(diào)函數(shù)fn保存在_events[event]中,對同一event可以綁定多個回調(diào)函數(shù),同時,通過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事件發(fā)生時,fn只會被調(diào)用一次,調(diào)用完成后通過$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:解除事件綁定,源碼可以看出它的三個調(diào)用方式:
vm.$off()
不帶參數(shù):將刪除組件所有綁定的事件(this._events = {}),在此之前,會遍歷更新父組件的計數(shù)器。
vm.$off(event)
只帶參數(shù)event:將刪除組件對event綁定的所有事件,同樣會遍歷更新父組件的計數(shù)器。
vm.$off(event, fn)
帶齊參數(shù)event和fn:將刪除組件對event事件綁定的fn回調(diào),同樣會遍歷更新父組件的計數(shù)器。
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:用于調(diào)用自身對event綁定的回調(diào)函數(shù)。該函數(shù)會被$broadcast和$dispatch調(diào)用,所以對參數(shù)的event進行了適配。部分變量備注:
isSource:是否是源組件發(fā)出的$emit事件。也就是說,只有直接調(diào)用vm.$emit事件或者$dispatch率先觸發(fā)自己綁定的回調(diào)($dispatch源碼第一行)的時候,參數(shù)是event字符串,此時isScource才為true。其他情況,如$broadcast內(nèi)部調(diào)用$emit,其參數(shù)會是一個非字符串,在下面的$broadcast和$dispatch可以看到,此時的參數(shù)會是{ name: event, source: this }。
event:由isSource可以得到:event即事件(字符串)。
shouldPropagate:是否需要繼續(xù)傳播事件觸發(fā)。源碼中,遍歷了event綁定的事件,除開(!hasParentCbs || cb._fromParent)這個不說,只要執(zhí)行的綁定事件明確return true,shouldPropagate才會置為true。對于$progress,如果shouldPropagate為true,會觸發(fā)繼續(xù)向下傳播事件。
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的理解差不多,指代是否最開始調(diào)用$broadcast。
這里vm._eventsCount[event]起到作用了,如果該計數(shù)為0,說明其所有子組件包括遞歸下去的子組件都沒有對event綁定回調(diào)。
從for循環(huán)的寫法可以看出,這里何時停止事件傳播使用的方法類似于深度優(yōu)先搜索(DFS)如下圖
A組件發(fā)出$broadcast,自身不會調(diào)用監(jiān)聽event的事件,而是傳遞給子組件,子組件B1率先執(zhí)行監(jiān)聽event的事件,其中有一個綁定事件return true,那么該B1繼續(xù)傳播事件,C1率先執(zhí)行,C1所有監(jiān)聽event的回調(diào)事件都沒有return true,所以C1不會往它的子組件傳播事件。
到此,只是遍歷完最左側(cè)的線,接下來輪到C2執(zhí)行,C2執(zhí)行后再決定是否需要傳遞給其子組件,接下來C3....執(zhí)行完B1的子組件,接下來就B2,然后...
從這里可以看出,如果某一層一個組件return true,那么會繼續(xù)遍歷新一層子組件,有點雪崩式的爆發(fā),return true或許會導致性能下降,這種事件通知的機制或許需要改善改善,因為假設(shè)我只要通知B1和C1,結(jié)果還是會遍歷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相對簡單,先觸發(fā)自身對event綁定的回調(diào),如果自己沒有監(jiān)聽event的回調(diào),則會繼續(xù)調(diào)用父組件觸發(fā)相應(yīng)綁定的事件。如果有回調(diào),還需要判斷_fromParent這個屬性,這個不知何物,待發(fā)掘。
假設(shè)A->B->C三層,B發(fā)出$dispatch("e"),想要B和A執(zhí)行,那么B需要return true; C發(fā)出$dispatch("e"),想要C和B執(zhí)行,那么C需要return true。但此時B也return true了,所以A也會觸發(fā)。所以如果遇到這種情況,可以修改dispatch的事件名字,比如C換成$dispatch("f");或者通過傳遞其他參數(shù)來判斷是否需要return true。(推薦前者,比較干凈)
總結(jié)Vue的eventsAPI是比較好理解的模塊,在看源碼以前,原以為$broadcast和$dispatch是在$nextTick實現(xiàn),現(xiàn)在才意識到是一調(diào)用便執(zhí)行。所以如果有多個地方會return true,還是需要考慮下用其他方法,不然會阻塞挺久的。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90970.html
摘要:目標是為了可以調(diào)試版本的,也就是下的源碼,所以主要是的開啟。結(jié)語至此就可以開心的研究源碼啦。文章鏈接源碼分析系列源碼分析系列之入口文件分析源碼分析系列之響應(yīng)式數(shù)據(jù)一源碼分析系列之響應(yīng)式數(shù)據(jù)二 概述 為了探究vue的本質(zhì),所以想debug一下源碼,但是怎么開始是個問題,于是有了這樣一篇記錄。目標是為了可以調(diào)試es6版本的,也就是src下的源碼,所以主要是sourceMap的開啟。原文來自...
摘要:至此算是找到了源碼位置。至此進入過渡的部分完畢。在動畫結(jié)束后,調(diào)用了由組件生命周期傳入的方法,把這個元素的副本移出了文檔流。這篇并沒有去分析相關(guān)的內(nèi)容,推薦一篇講非常不錯的文章,對構(gòu)造函數(shù)如何來的感興趣的同學可以看這里 Vue transition源碼分析 本來打算自己造一個transition的輪子,所以決定先看看源碼,理清思路。Vue的transition組件提供了一系列鉤子函數(shù),...
摘要:本次分析的版本是。持續(xù)更新中。。。目錄的引入的實例化的引入這一章將會分析用戶在引入后,框架做的初始化工作創(chuàng)建這個類,并往類上添加類屬性類方法和實例屬性實例方法。 背景 Vue.js是現(xiàn)在國內(nèi)比較火的前端框架,希望通過接下來的一系列文章,能夠幫助大家更好的了解Vue.js的實現(xiàn)原理。本次分析的版本是Vue.js2.5.16。(持續(xù)更新中。。。) 目錄 Vue.js的引入 Vue的實例化...
摘要:數(shù)據(jù)驅(qū)動一個核心思想是數(shù)據(jù)驅(qū)動。發(fā)生了什么從入口代碼開始分析,我們先來分析背后發(fā)生了哪些事情。函數(shù)最后判斷為根節(jié)點的時候設(shè)置為,表示這個實例已經(jīng)掛載了,同時執(zhí)行鉤子函數(shù)。這里注意表示實例的父虛擬,所以它為則表示當前是根的實例。 數(shù)據(jù)驅(qū)動 Vue.js 一個核心思想是數(shù)據(jù)驅(qū)動。所謂數(shù)據(jù)驅(qū)動,是指視圖是由數(shù)據(jù)驅(qū)動生成的,我們對視圖的修改,不會直接操作 DOM,而是通過修改數(shù)據(jù)。它相比我們傳...
摘要:應(yīng)用啟動一般是通過,所以,先從該構(gòu)造函數(shù)著手。構(gòu)造函數(shù)文件該文件只是構(gòu)造函數(shù),原型對象的聲明分散在當前目錄的多個文件中構(gòu)造函數(shù)接收參數(shù),然后調(diào)用。 源碼版本:v2.1.10 分析目標 通過閱讀源碼,對 Vue2 的基礎(chǔ)運行機制有所了解,主要是: Vue2 中數(shù)據(jù)綁定的實現(xiàn)方式 Vue2 中對 Virtual DOM 機制的使用方式 源碼初見 項目構(gòu)建配置文件為 build/conf...
閱讀 3859·2021-09-29 09:34
閱讀 3786·2021-09-27 13:34
閱讀 580·2021-09-24 09:47
閱讀 3046·2019-08-30 15:53
閱讀 1821·2019-08-26 13:54
閱讀 2096·2019-08-26 13:43
閱讀 545·2019-08-23 14:47
閱讀 1752·2019-08-23 14:28