日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

Vue源码探究-事件系统

發布時間:2025/3/15 vue 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Vue源码探究-事件系统 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Vue源碼探究-事件系統

本篇代碼位于vue/src/core/instance/events.js

緊跟著生命周期之后的就是繼續初始化事件相關的屬性和方法。整個事件系統的代碼相對其他模塊來說非常簡短,分幾個部分來詳細看看它的具體實現。

頭部引用

import {tip,toArray,hyphenate,handleError,formatComponentName } from '../util/index' import { updateListeners } from '../vdom/helpers/index'

頭部先是引用了的一些工具方法,沒有什么難點,具體可以查看相應文件。唯一值得注意的是引用自虛擬節點模塊的一個叫 updateListeners 方法。顧名思義,是用來更新監聽器的,至于為什么要有這樣的一個方法,主要是因為如果該實例的父組件已經存在一些事件監聽器,為了正確捕獲到事件并向上冒泡,父級事件是需要繼承下來的,這個原因在下面的初始化代碼中有佐證;另外,如果在實例初始化的時候綁定了同名的事件處理器,也需要為同名事件添加新的處理器,以實現同一事件的多個監聽器的綁定。

事件初始化

// 定義并導出initEvents函數,接受Component類型的vm參數 export function initEvents (vm: Component) {// 創建例的_events屬性,初始化為空對象vm._events = Object.create(null)// 創建實例的_hasHookEvent屬性,初始化為falsevm._hasHookEvent = false// 初始化父級附屬事件// init parent attached eventsconst listeners = vm.$options._parentListeners// 如果父級事件存在,則更新實例事件監聽器if (listeners) {updateComponentListeners(vm, listeners)} }// 設置target值,目標是引用實例 let target: any// 添加事件函數,接受事件名稱、事件處理器、是否一次性執行三個參數 function add (event, fn, once) {if (once) {target.$once(event, fn)} else {target.$on(event, fn)} }// 移除事件函數,接受事件名稱和時間處理器兩個參數 function remove (event, fn) {target.$off(event, fn) }// 定義并導出函數updateComponentListeners,接受實例對象,新舊監聽器參數 export function updateComponentListeners (vm: Component,listeners: Object,oldListeners: ?Object ) {// 設置target為vmtarget = vm// 執行更新監聽器函數,傳入新舊事件監聽對象、添加事件與移除事件函數、實例對象updateListeners(listeners, oldListeners || {}, add, remove, vm)// 置空引用target = undefined }

如上述代碼所示,事件監聽系統的初始化首先是創建了私有的事件對象和是否有事件鉤子的標志兩個屬性,然后根據父級是否有事件處理器來決定是否更新當前實例的事件監聽器,具體如何實現監聽器的更新,貼上這段位于虛擬節點模塊的輔助函數中的代碼片段來仔細看看。

更新事件監聽器

// 定義并導出updateListeners哈數 // 接受新舊事件監聽器對象,事件添加和移除函數以及實例對象參數。 export function updateListeners (on: Object,oldOn: Object,add: Function,remove: Function,vm: Component ) {// 定義一些輔助變量let name, def, cur, old, event// 遍歷新的監聽器對象for (name in on) {// 為def和cur賦值為新的事件對象def = cur = on[name]// 為old賦值為舊的事件對象old = oldOn[name]// 標準化事件對象并賦值給event。// normalizeEvent函數主要用于將傳入的帶有特殊前綴的事件修飾符分解為具有特定值的事件對象event = normalizeEvent(name)// 下面代碼是weex框架專用,處理cur變量和格式化好的事件對象的參數屬性/* istanbul ignore if */if (__WEEX__ && isPlainObject(def)) {cur = def.handlerevent.params = def.params}// 如果新事件不存在,在非生產環境中提供報錯信息,否則不執行任何操作if (isUndef(cur)) {process.env.NODE_ENV !== 'production' && warn(`Invalid handler for event "${event.name}": got ` + String(cur),vm)// 當舊事件不存在時} else if (isUndef(old)) {// 如果新事件對象cur的fns屬性不存在if (isUndef(cur.fns)) {// 創建函數調用器并重新復制給cur和on[name]cur = on[name] = createFnInvoker(cur)}// 添加新的事件處理器add(event.name, cur, event.once, event.capture, event.passive, event.params)// 如果新舊事件不完全相等} else if (cur !== old) {// 用新事件處理函數覆蓋舊事件對象的fns屬性old.fns = cur// 將事件對象重新復制給onon[name] = old}}// 遍歷舊事件監聽器for (name in oldOn) {// 如果新事件對象不存在if (isUndef(on[name])) {// 標準化事件對象event = normalizeEvent(name)// 移除事件處理器remove(event.name, oldOn[name], event.capture)}} }

這段代碼中用到了 normalizeEvent 和 createFnInvoker 兩個主要的函數來完成更新監聽器的實現,代碼與 updateListeners 函數位于同一文件中。

  • normalizeEvent:主要是用于返回一個定制化的事件對象,這個函數接受4個必選參數和2兩個可選參數,分別是事件名稱name屬性、是否一次性執行的once屬性、是否捕獲事件的capture屬性、是否使用被動模式passive屬性、事件處理器handler方法、事件處理器參數params數組。屬性的含義都比較好理解,特別注意一下 once、capture、passive 屬性,這三個屬性是用來修飾事件的,分別對應了 ~、!、& 修飾符,貼上一個官方文檔中的使用示例,引用自事件 & 按鍵修飾符。啟動被動模式的用途是使事件處理器無法阻止默認事件,比如 <a> 標簽自帶的鏈接跳轉事件,如果設置passive為true,則事件處理器即便是設置了阻止默認事件也是沒辦法阻止跳轉的。
on: {'!click': this.doThisInCapturingMode,'~keyup': this.doThisOnce,'~!mouseover': this.doThisOnceInCapturingMode }
  • createFnInvoker: 接受一個fns參數,可以傳入一個事件處理器函數,也可以傳入一個包含多個處理器的數組。在該函數內部定義了一個 invoker 函數并且最終返回它,函數有一個fns屬性是用來存放所傳入的處理器的,調用這個函數后,會按fns的類型來分別執行處理器數組的調用或單個處理器的調用。這個實現即是真正執行事件處理器調用的過程。

事件相關的原型方法

在事件的初始化過程里有用到幾個以 & 開頭的類原型方法,它們是在mixin函數里掛載到核心類上的。初始化的時候定義的方法都是在這些方法的基礎上再進行了一次封裝,其綁定事件、觸發事件和移除事件的具體實現都在這些方法中,當然不會放過對這些細節的探索。

// 導出eventsMixin函數,接收形參Vue, // 使用Flow進行靜態類型檢查指定為Component類 export function eventsMixin (Vue: Class&lt;Component&gt;) {// 定義hook正則檢驗const hookRE = /^hook:/// 給Vue原型對象掛載$on方法// 參數event可為字符串或數組類型,fn是事件監聽函數// 方法返回實例對象本身Vue.prototype.$on = function (event: string | Array&lt;string&gt;, fn: Function): Component {// 定義實例變量const vm: Component = this// 如果傳入的event參數是數組,遍歷event數組,為所有事件注冊fn監聽函數if (Array.isArray(event)) {for (let i = 0, l = event.length; i &lt; l; i++) {this.$on(event[i], fn)}} else {// event參數為字符串時,檢查event事件監聽函數數組是否存在// 已存在事件監聽數組則直接添加新監聽函數// 否則建立空的event事件監聽函數數組,再添加新監聽函數(vm._events[event] || (vm._events[event] = [])).push(fn)// 此處做了性能優化,使用正則檢驗hook:是否存在的布爾值// 而不是hash值查找設置實例對象的_hasHookEvent值// 此次優化是很久之前版本的修改,暫時不太清楚以前hash值查找是什么邏輯,留待以后查證// optimize hook:event cost by using a boolean flag marked at registration// instead of a hash lookupif (hookRE.test(event)) {vm._hasHookEvent = true}}// 返回實例本身return vm}// 為Vue原型對象掛載$once方法// 參數event只接受字符串,fn是監聽函數Vue.prototype.$once = function (event: string, fn: Function): Component {// 定義實例變量const vm: Component = this// 創建on函數function on () {// 函數執行后先清除event事件綁定的on監聽函數,即函數本身// 這樣以后就不會再繼續監聽event事件vm.$off(event, on)// 在實例上運行fn監聽函數fn.apply(vm, arguments)}// 為on函數設置fn屬性,保證在on函數內能夠正確找到fn函數on.fn = fn// 為event事件注冊on函數vm.$on(event, on)// 返回實例本身return vm}// 為Vue原型對象掛載$off方法// event參數可為字符串或數組類型// fn是監聽函數,為可選參數Vue.prototype.$off = function (event?: string | Array&lt;string&gt;, fn?: Function): Component {// 定義實例變量const vm: Component = this// 如果沒有傳入參數,則清除實例對象的所有事件// 將實例對象的_events私有屬性設置為null,并返回實例// allif (!arguments.length) {vm._events = Object.create(null)return vm}// 如果event參數傳入數組,清除所有event事件的fn監聽函數返回實例// 這里是$off方法遞歸執行,最終會以單一事件為基礎來實現監聽的清除// array of eventsif (Array.isArray(event)) {for (let i = 0, l = event.length; i &lt; l; i++) {this.$off(event[i], fn)}return vm}// 如果指定單一事件,將事件的監聽函數數組賦值給cbs變量// specific eventconst cbs = vm._events[event]// 如果沒有注冊此事件監聽則返回實例if (!cbs) {return vm}// 如果沒有指定監聽函數,則清除所有該事件的監聽函數,返回實例if (!fn) {vm._events[event] = nullreturn vm}// 如果指定監聽函數,則遍歷事件監聽函數數組,移除指定監聽函數返回實例if (fn) {// specific handlerlet cblet i = cbs.lengthwhile (i--) {cb = cbs[i]if (cb === fn || cb.fn === fn) {cbs.splice(i, 1)break}}}return vm}// 為Vue原型對象掛載$emit方法,只接受單一eventVue.prototype.$emit = function (event: string): Component {// 定義實例變量const vm: Component = this// 在非生產環境下,傳入的事件字符串如果是駝峰值且有相應的小寫監聽事件// 則提示事件已注冊,且無法使用駝峰式注冊事件if (process.env.NODE_ENV !== 'production') {const lowerCaseEvent = event.toLowerCase()if (lowerCaseEvent !== event &amp;&amp; vm._events[lowerCaseEvent]) {tip(`Event "${lowerCaseEvent}" is emitted in component ` +`${formatComponentName(vm)} but the handler is registered for "${event}". ` +`Note that HTML attributes are case-insensitive and you cannot use ` +`v-on to listen to camelCase events when using in-DOM templates. ` +`You should probably use "${hyphenate(event)}" instead of "${event}".`)}}// 將事件監聽函數數組賦值 給cbslet cbs = vm._events[event]// 如果監聽函數數組存在if (cbs) {// 重置cbs變量,為何要使用toArray方法轉換一次數組不太明白?cbs = cbs.length &gt; 1 ? toArray(cbs) : cbs// 將event之后傳入的所有參數定義為args數組const args = toArray(arguments, 1)// 遍歷所有監聽函數,為實例執行每一個監聽函數,并傳入args參數數組for (let i = 0, l = cbs.length; i &lt; l; i++) {try {cbs[i].apply(vm, args)} catch (e) {handleError(e, vm, `event handler for "${event}"`)}}}return vm} }

eventsMixin的內容非常直觀,分別為實例原型對象掛載了$on、$once、$off、$emit四個方法。這是實例事件監聽函數的注冊、一次性注冊、移除和觸發的內部實現。在使用的過程中會對這些實現有一個更清晰的理解。


終于對Vue的事件系統的實現有了一個大致了解,沒有什么特別高深的處理,但完整的事件系統的實現有很多細致的功能這里其實并沒有特別詳細地探討,比如事件修飾符,可以參考官方文檔里的解說會有一個更清晰的了解。事件系統的重要作用首先是為實例制定了一套處理事件的方案和標準,其次是在實例數據更新的過程中保持對事件監聽器的更新,這兩個部分的處理是最需要細致去琢磨的。

原文地址:https://segmentfault.com/a/1190000016757343

轉載于:https://www.cnblogs.com/lalalagq/p/9901082.html

總結

以上是生活随笔為你收集整理的Vue源码探究-事件系统的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。