Learn from zepto.js how to trigger DOM events manually

Posted by 244863 on Mon, 24 Jun 2019 22:52:38 +0200

Preface

Front-end is very popular in recent years, vue, react, angular frameworks emerge in endlessly, if we do not know a two-way data binding, do not know what is virtual DOM, it may be despised. Behind the heat is often endless impetuosity, learning these advanced and popular libraries or frameworks can make us go faster, but calm down to return to the foundation, lay a solid foundation, but can let us go more stable and farther.

I've been looking at Zepto's source code lately, hoping to learn some framework design skills, and to refresh and consolidate the js foundation that I haven't picked up for a long time. If you are interested in this series, you are welcome to click the address below watch and keep an eye on developments. This article mainly talks about the trigger implementation principle of event. js in zepto.

Original address

Warehouse Address

<!--more-->

event.js module

Zepto is composed of many small modules, including basic zepto.js module, event.js event processing module, ajax.js request processing module and so on. The core of event.js event processing module completes event binding on in zepto, removing off and trigger ing manually. Let's briefly review how to use these three functions of 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)

// Remove events

// We can specify an event handler to remove click events
$list.off('click', cb1)
// click events can also be removed directly
$list.off('click')

// Manual trigger event
$list.trigger('click', 'qianlongo')

Dude, you tease me, jQuery, how familiar zepto is, who won't use this! Don't worry objectively. Today we're going to take a look at how the source code works.

Step by step to see how trigger is implemented

Code directly

$.fn.trigger = function (event, args) {
  // Processing the incoming event, if it is a string or a pure object, gets a self-created event object
  // If the incoming object is already a $. Event-processed object, put it in compatible and revamp it again (actually adding several methods and rewriting several methods)
  event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
  // Parameters passed by args to event handlers
  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
    // Triggering dom events
    else if ('dispatchEvent' in this) this.dispatchEvent(event)
    // Because elements inside zepto objects are not necessarily dom elements, callback functions are triggered directly
    else $(this).triggerHandler(event, args)
  })
}

Posting the trigger function code directly may be confusing.

What is it?!!

What is $. fn?!!

What is $. isPlainObject?!!

What the hell is Event?

There seems to be a series of problems waiting for us to solve.

To make it difficult to understand directly, let's first look at how zepto adds functionality to the underlying zepto.js module.

First look at the zepto.js module

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)

By removing unnecessary code as much as possible, you can see that the Zepto we usually use is actually a function derived internally from its anonymous function. And $. fn is the prototype.

How to add function to zepto.js module

zepto.js module only has some basic functions, such as adding events to dom, but not. How to add them?


(function ($) {
  // xxx
  $.fn.on = function () {//xxxx}
  $.fn.off = function () {//xxxx}
  $.fn.trigger = function () {//xxxx}
  $.Event = function () {//xxx}
  // xxx
})(Zepto)

Finally, by reducing other interference codes, you can see that the so-called addition of functionality to the zepto.js module basically adds new methods to its prototype or specifies some static methods directly on the $function.

Now that we've settled the question of $, $. fn, let's go back and start explaining step by step how to trigger events manually.

Revisiting the source code of trigger function

$.fn.trigger = function (event, args) {
  // Processing the incoming event, if it is a string or a pure object, gets a self-created event object
  // If the incoming object is already a $. Event-processed object, put it in compatible and revamp it again (actually adding several methods and rewriting several methods)
  event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
  // Parameters passed by args to event handlers
  event._args = args

  // xxx
}

First, we delete some of the following code, let's understand these sentences. One of the most important functions is $. Event.

IsString => Judgment is not a string?

IsPlainObject => Judges whether or not the object is a preserved object (it must be an object, except a window object, whose prototype must be identical to that of Object)?

Compatible => is actually an extension of event object, such as adding some methods, rewriting some methods, and so on.

These methods do not require much attention for the time being.

Let's look at $. Event, which contains almost all the steps and content of how to trigger a dom event manually.

Let's look at $. Event, which contains almost all the steps and content of how to trigger a dom event manually.

Let's look at $. Event, which contains almost all the steps and content of how to trigger a dom event manually.

What is Event?

Create and initialize a specified dom event object, and if props are given, extend it to the event object

 $.Event = function (type, props) {
  // When type is an object, such as {type:'click', data:'qianlongo'}
  if (!isString(type)) props = type, type = props.type
  // click,mousedown,mouseup mousemove corresponds to MouseEvent
  // Other events correspond to Events
  // And set bubbles to true to indicate event bubbles, while false does not bubbles.
  var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
  // When props exist, props are processed circularly, and their attributes are extended to event objects.
  if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
  // Initialize the event object, the first is the event type, the second is bubbling or not, and the third is whether the browser default behavior can be prevented by preventDefault
  event.initEvent(type, bubbles, true)
  // Processing the created time object and returning it
  return compatible(event)
}

The comment is clearly written, and this function returns an initialized event object.

Here's a quick summary of the basic steps to trigger a dom event manually

Manually triggering a dom event takes three steps, if you are right document.createEvent Not very familiar, you can click to see.

  1. Create an event object document.createEvent(event)

  2. Initialize event object event.initEvent(type, bubbles, true)

  3. Distribution event dom.dispatchEvent(event)

Now that we've done the first two steps, we've got the last one, so let's look at the rest of the trigger code.

Manually trigger the last step of dom event

 $.fn.trigger = function (event, args) {
  // Processing the incoming event, if it is a string or a pure object, gets a self-created event object
  // If the incoming object is already a $. Event-processed object, put it in compatible and revamp it again (actually adding several methods and rewriting several methods)
  event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
  // Parameters passed by args to event handlers
  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
    // Triggering dom events
    else if ('dispatchEvent' in this) this.dispatchEvent(event)
    // Because elements inside zepto objects are not necessarily dom elements, callback functions are triggered directly
    else $(this).triggerHandler(event, args)
  })
}

The last step is actually to do an each traversal of the currently selected element, and then determine whether the event to trigger is focus or blur, and if so, execute it directly.

Further, if the dispatchEvent method exists in the current dom element attribute, the event is triggered. Why this step? Because we know that there are many ways to use the $() function, and some ways to get a zepto object are without selecting the dom node.

Finally, there's an else branch, which handles events not manually but directly when registered (because it involves how to manage the mapping relationship between elements and event queues in the zepto event module, which will take a long time and will not be explained in the next article).

Manual diy one

According to the above description, triggering DOM events manually is not so magical. The goal can be achieved by completing three steps. Let's write an example manually by ourselves.


<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. Create an event object document.createEvent(event)
let event = document.createEvent('Event')
// 2. Initialize event.initEvent(type, bubbles, true)
event.initEvent('click', true, true)
// 3. Distribution event dom.dispatchEvent(event)
$item1.dispatchEvent(event)

At this point the console prints out

1

<li class="item1">1</li>
<li>2</li>
<li>3</li>

1 is printed out by the event handler of item 1.

The back part of li is printed out by list.

If the second parameter of initEvent is set to false, bubbles will not be allowed, and only 1 will be printed out.

Ending

If this part helps you a little, how about a star? (viii), (viii)

Warehouse Address

Topics: Javascript Vue React angular JQuery