向zepto.js學習如何手動(trigger)觸發DOM事件

謙龍發表於2017-06-06

前言

前端在最近幾年實在火爆異常,vue、react、angular各路框架層出不窮,我們們要是不知道個雙向資料繫結,不曉得啥是虛擬DOM,也許就被鄙視了。火熱的背後往往也是無盡的浮躁,學習這些先進流行的類庫或者框架可以讓我們走的更快,但是靜下心來回歸基礎,把基石打牢固,卻可以讓我們走的更穩,更遠。

最近一直在看zepto的原始碼,希望通過學習它掌握一些框架設計的技巧,也將很久不再拾起的js基礎重新溫習鞏固一遍。如果你對這個系列感興趣,歡迎點選下方地址watch,隨時關注動態。這篇文章主要想說一下zepto中事件模組(event.js)的trigger實現原理。

原文地址

倉庫地址

向zepto.js學習如何手動(trigger)觸發DOM事件

event.js模組

zepto中由許多小的模組組合合成,基礎的zepto.js模組,event.js事件處理模組,ajax.js請求處理模組等等。其中event.js事件處理模組的核心完成了zepto中事件繫結on,移除off還有手動觸發trigger等功能。我們簡單回顧下如何使用zepto的這三大功能。


<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')複製程式碼

向zepto.js學習如何手動(trigger)觸發DOM事件

哥們你逗我呢,jQuery,zepto多熟了,誰不會用這個啊!客觀別急,我們今天主要是慢慢來看看它原始碼怎麼實現的。

一步步看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)
  })
}複製程式碼

直接貼出trigger函式的程式碼可能我們是懵逼的

$是啥啊!!!

$.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是啥的疑問了,現在回去開始一步步解讀如何實現手動觸發事件。

重新看trigger函式原始碼

$.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事件的大部分步驟和內容。

$.Event是個啥

建立並初始化一個指定的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,不是很熟悉,可以點選檢視。

  1. 建立一個事件物件 document.createEvent(event)
  2. 初始化事件物件 event.initEvent(type, bubbles, true)
  3. 分發事件 dom.dispatchEvent(event)

到這裡已經完成了前面兩步,還剩最後一步了,在來看trigger剩下的程式碼

手動觸發dom事件最後一步

 $.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事件模組中如何管理元素與事件佇列的對映關係,篇幅會比較長,會在接下來的文章中說,這裡不展開說明)

手動diy一個

根據上面的描述,手動觸發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好不好呀! ???

倉庫地址

相關文章