前端在最近幾年實在火爆異常,vue、react、angular各路框架層出不窮,我們要是不知道個雙向數據綁定,不曉得啥是虛擬DOM,也許就被鄙視了。火熱的背後每每也是無盡的浮躁,學習這些先進流行的類庫或者框架可讓咱們走的更快,可是靜下心來回歸基礎,把基石打牢固,卻可讓咱們走的更穩,更遠。javascript
最近一直在看zepto的源碼,但願經過學習它掌握一些框架設計的技巧,也將好久再也不拾起的js基礎從新溫習鞏固一遍。若是你對這個系列感興趣,歡迎點擊下方地址watch
,隨時關注動態。這篇文章主要想說一下zepto中事件模塊(event.js)的trigger
實現原理。html
原文地址前端
倉庫地址vue
<!--more-->java
zepto中由許多小的模塊組合合成,基礎的
zepto.js
模塊,event.js
事件處理模塊,ajax.js
請求處理模塊等等。其中event.js
事件處理模塊的核心完成了zepto中事件綁定on
,移除off
還有手動觸發trigger
等功能。咱們簡單回顧下如何使用zepto的這三大功能。react
<ul class="list"> <li>1</li> <li>2</li> </ul>
let $list = $('.list') let cb1 = function (e, name) { console.log(1, name) } let cb2 = function (e, name) { console.log(2, name) } $list.on('click', cb1) $list.on('click', cb2) // 移除事件 // 咱們能夠指定移除click事件的某個事件處理程序 $list.off('click', cb1) // 也能夠直接移除click事件 $list.off('click') // 手動觸發事件 $list.trigger('click', 'qianlongo')
哥們你逗我呢,jQuery,zepto多熟了,誰不會用這個啊!客觀別急,咱們今天主要是慢慢來看看它源碼怎麼實現的。git
直接上代碼github
$.fn.trigger = function (event, args) { // 對傳入的event進行處理,若是是字符串或者純對象,獲得一個本身建立的事件對象 // 若是傳入的已是個通過$.Event處理的對象,則放入compatible再次改造(其實就是添加了幾個方法,和重寫了幾個方法) event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event) // args傳遞給事件處理程序的參數 event._args = args return this.each(function () { // handle focus(), blur() by calling them directly if (event.type in focus && typeof this[event.type] == "function") this[event.type]() // items in the collection might not be DOM elements // 觸發dom事件 else if ('dispatchEvent' in this) this.dispatchEvent(event) // 由於zepto對象內部的元素不必定是dom元素,此時直接觸發回調函數 else $(this).triggerHandler(event, args) }) }
直接貼出trigger函數的代碼可能咱們是懵逼的ajax
$
是啥啊!!!瀏覽器
$.fn
是啥啊!!!
$.isPlainObject
又是啥啊!!!
$.Event
又是什麼鬼?
彷彿有一連串的問題等待着咱們解決。
爲了直接切入不易理解,咱們先來看看zepto中是如何給基礎的zepto.js
模塊添加功能的
首先看看zepto.js模塊
var Zepto = (function () { // xxxx var $ = function (selector, context) { return zepto.init(selector, context) } return $ zepto.Z.prototype = Z.prototype = $.fn // xxxx })() window.Zepto = Zepto window.$ === undefined && (window.$ = Zepto)
儘可能刪除了一些沒必要要的代碼,能夠看到咱們平時使用的Zepto
其實就是其匿名函數自執行內部導出的一個函數。而$.fn
就是其原型
如何給zepto.js模塊增添功能
zepto.js
模塊只有一些基礎的功能,好比給dom添加事件的功能就沒有,怎麼添加呢?
(function ($) { // xxx $.fn.on = function () {//xxxx} $.fn.off = function () {//xxxx} $.fn.trigger = function () {//xxxx} $.Event = function () {//xxx} // xxx })(Zepto)
最後縮減掉其餘的干擾代碼,能夠看到所謂的給zepto.js
模塊增添功能,基本上就是在其原型上添加新的方法或者直接在$函數上定一些靜態方法。
好啦咱們已經解決了$
,$.fn
是啥的疑問了,如今回去開始一步步解讀如何實現手動觸發事件。
$.fn.trigger = function (event, args) { // 對傳入的event進行處理,若是是字符串或者純對象,獲得一個本身建立的事件對象 // 若是傳入的已是個通過$.Event處理的對象,則放入compatible再次改造(其實就是添加了幾個方法,和重寫了幾個方法) event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event) // args傳遞給事件處理程序的參數 event._args = args // xxx }
先把後面的一些代碼給刪除了,咱們先理解這幾句代碼。其中很是重要的一個函數就是$.Event
,至於
isString
=> 判斷是否是字符串
isPlainObject
=> 判斷是否是存粹的對象(必須是對象,window對象除外,該對象的原型必須和Object的原型一致)
compatible
=> 其實就是對事件對象event
作一些擴展,好比添加一些方法,重寫一些方法之類的。
這幾個方法暫時能夠不須要太多關心.
咱們主要看看$.Event
,這裏面幾乎含有如何手動觸發一個dom事件的大部分步驟和內容。
咱們主要看看$.Event
,這裏面幾乎含有如何手動觸發一個dom事件的大部分步驟和內容。
咱們主要看看$.Event
,這裏面幾乎含有如何手動觸發一個dom事件的大部分步驟和內容。
建立並初始化一個指定的dom事件對象, 若是給定了props,則將其擴展到事件對象上
$.Event = function (type, props) { // 當type是個對象時,好比{type: 'click', data: 'qianlongo'} if (!isString(type)) props = type, type = props.type // click,mousedown,mouseup mousemove對應MouseEvent // 其餘事件對應爲Events // 並把bubbles設置爲true,表示事件冒泡,爲false則不冒泡 var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true // 當props存在的時候,對props進行循環處理,將其屬性擴展到event對象上 if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) // 初始化事件對象,第一個爲事件類型,第二個爲冒泡與否,第三個爲是否能夠經過preventDefault來阻止瀏覽器默認行爲 event.initEvent(type, bubbles, true) // 再對創造出來的時間對象處理一番並返回 return compatible(event) }
註釋已經寫的很清楚了,這個函數就是返回一個通過初始化了的事件對象
到這裏咱們直接概括一下要手動觸發一個dom事件的基本步驟
手動觸發一個dom事件,須要3步,若是你對document.createEvent,不是很熟悉,能夠點擊查看。
建立一個事件對象 document.createEvent(event)
初始化事件對象 event.initEvent(type, bubbles, true)
分發事件 dom.dispatchEvent(event)
到這裏已經完成了前面兩步,還剩最後一步了,在來看trigger
剩下的代碼
$.fn.trigger = function (event, args) { // 對傳入的event進行處理,若是是字符串或者純對象,獲得一個本身建立的事件對象 // 若是傳入的已是個通過$.Event處理的對象,則放入compatible再次改造(其實就是添加了幾個方法,和重寫了幾個方法) event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event) // args傳遞給事件處理程序的參數 event._args = args return this.each(function () { // handle focus(), blur() by calling them directly if (event.type in focus && typeof this[event.type] == "function") this[event.type]() // items in the collection might not be DOM elements // 觸發dom事件 else if ('dispatchEvent' in this) this.dispatchEvent(event) // 由於zepto對象內部的元素不必定是dom元素,此時直接觸發回調函數 else $(this).triggerHandler(event, args) }) }
最後一步其實就是將當前選中的元素進行一次each遍歷,而後判斷要觸發的事件是否是focus
或者blur
,若是是就直接執行。
再進一步,若是dispatchEvent
方法在當前的dom元素屬性中存在,那麼便將該事件觸發。(爲何要這一步呢?由於咱們知道$()函數的使用方式有不少,有些方式獲得的zepto對象是沒有選中dom節點的)
最後還有一個else分支,這個分支處理走的不是手動觸發事件,而是直接觸發註冊事件時添加的事件處理程序(由於這裏涉及到zepto事件模塊中如何管理元素與事件隊列的映射關係,篇幅會比較長,會在接下來的文章中說,這裏不展開說明)
根據上面的描述,手動觸發DOM事件,原來並無那麼神奇,完成三步,便可達到目標。咱們本身來手動寫一個示例
<ul class="list"> <li class="item1">1</li> <li>2</li> <li>3</li> </ul>
let $list = document.querySelector('.list') let $item1 = document.querySelector('.item1') $list.addEventListener('click', function () { console.log(this.innerHTML) }, false) $item1.addEventListener('click', function () { console.log(this.innerHTML) }, false) // 1. 建立一個事件對象 document.createEvent(event) let event = document.createEvent('Event') // 2. 初始化事件對象 event.initEvent(type, bubbles, true) event.initEvent('click', true, true) // 3. 分發事件 dom.dispatchEvent(event) $item1.dispatchEvent(event)
這個時候控制檯打印出了
1 <li class="item1">1</li> <li>2</li> <li>3</li>
1是item1的事件處理函數打印出來的。
後面的li那部分則是list打印出來的。
若是將initEvent的第二個參數設置爲false,將不容許冒泡,則只會打印出1
若是這部分對你有點點幫助,點個star好很差呀! ???