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