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

前言

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

最近一直在看zepto的源碼,但願經過學習它掌握一些框架設計的技巧,也將好久再也不拾起的js基礎從新溫習鞏固一遍。若是你對這個系列感興趣,歡迎點擊下方地址watch,隨時關注動態。這篇文章主要想說一下zepto中事件模塊(event.js)的trigger實現原理。html

原文地址前端

倉庫地址vue

<!--more-->java

event.js模塊

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

一步步看trigger怎麼實現

直接上代碼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是啥的疑問了,如今回去開始一步步解讀如何實現手動觸發事件。

從新看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好很差呀! ???

倉庫地址

相關文章
相關標籤/搜索