Event module reading Zepto source code

Keywords: Javascript Attribute github JQuery

Event module is one of Zepto's essential modules. Event object is complex because of its unfamiliar with Event Api. So at first glance, the source code of Event module is a bit confusing. Looking at it carefully, it's actually not too complicated.

Read the Zepto source series on github. Welcome star: reading-zepto

Source version

The source code for this article is zepto1.2.0

Preparing knowledge

Event simulation of focus/blur

Why simulate focus and blur events? As you can see from MDN, focus events and blur events do not support event bubbling. The direct consequence of not supporting event bubbling is that event delegation cannot be performed, so focus and blur events need to be simulated.

In addition to focus events and blur events, modern browsers also support focusin g events and focusout events. The main difference between them and focus events and blur events is that they support event bubbles. Therefore, we can use focus and simulate the bubbling behavior of focus events, and focus out events to simulate the bubbling behavior of blur events.

We can determine the execution order of these four events by the following code:

<input id="test" type="text" />
const target = document.getElementById('test')
target.addEventListener('focusin', () => {console.log('focusin')})
target.addEventListener('focus', () => {console.log('focus')})
target.addEventListener('blur', () => {console.log('blur')})
target.addEventListener('focusout', () => {console.log('focusout')})

Under chrome59, when input focuses and defocuses, the console prints the following results:

'focus'
'focusin'
'blur'
'focusout'

As you can see, in this browser, the order of execution of events should be focus > focusin > Blur > focusout

For a more detailed description of these events, you can see:< Talk about focus/focusin/focusout/blur events>

As for the execution sequence of events, the results of my tests are somewhat different from those described in the article. If you are interested, you can click on this link to test it. http://jsbin.com/nizugazamo/edit?html,js,console,output . But I don't think it's necessary to go into the execution sequence. Fousin can be used as a bubbling version of the focus event.

Event simulation of mouseenter/mouseleave

Like focus and blur, mouseenter and mouseleave do not support event bubbling, but mouseover and mouseout support event bubbling. Therefore, mouseover and mouseout can be used to simulate the bubbling of these two events, respectively.

In the event object of the mouse event, there is a relatedTarget attribute from MDN:MouseEvent.relatedTarget In the document, you can see that mouseover's relatedTarget points to the exited from when it moves to the target node, and mouseout's relatedTarget points to the entry to when it leaves the destination node.

In addition, the mouseover event will be triggered as the mouse moves, but the mouseenter event will only be triggered once at the moment it enters the node. If the mouse is already on the target node, the relatedTarget when the mouseover event triggers is the current node.

Therefore, to simulate a mouseenter or a mouseleave event, it is only necessary to determine that the relatedTarget on the triggered mouseover or mouseout event does not exist, or that the relatedTarget is not the current node, and is not the child node of the current node, so as to avoid the impact of bubbling of the child node event.

On the simulation of mouseenter and mouseleave, Qianlong There is an article< Why are mouseenter and mouseover so entangled? > It's clearly written. I suggest you read it.

The Core of Event Module

The Event module is simplified as follows:

;(function($){})(Zepto)

In fact, it is to pass in the Zepto object to the closure, and then make some extensions to the Zepto object.

In the Event module, the following main things are done:

  • Provide concise API s
  • Unifying event Objects in Different Browsers
  • Event Handle Cache Pool facilitates manual triggering and unbinding of events.
  • Event Delegation

Internal approach

zid

var _zid = 1
function zid(element) {
  return element._zid || (element._zid = _zid++)
}

Gets the _zid attribute of the parameter element object. If the attribute does not exist, the global variable _zid is increased by 1, which is returned as the attribute value of _zid of the element. This method is used to mark elements that have been bound to events for easy searching.

parse

function parse(event) {
  var parts = ('' + event).split('.')
  return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
}

In zepto, event-supporting namespaces can be added to events in the form of eventType.ns1.ns2....

The parse function is used to decompose event names and namespaces.

''+event is to change event into a string and then to split it into an array.

In the returned object, e is the event name, ns is sorted, and the namespace string is connected by spaces, in the form of ns1 ns2 ns3.

matcherFor

function matcherFor(ns) {
  return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
}

Generate expressions that match namespaces. For example, the incoming parameter ns is ns1 ns2 ns3, and the final generated regularity is /(?: ^ |) ns1. *? Ns2. *? NS3 (?: |$)/. As for what's the use, I'll talk about it in a minute.

findHandlers, Handles to Find Cache

handlers = {}
function findHandlers(element, event, fn, selector) {
  event = parse(event)
  if (event.ns) var matcher = matcherFor(event.ns)
  return (handlers[zid(element)] || []).filter(function(handler) {
    return handler
      && (!event.e  || handler.e == event.e)
      && (!event.ns || matcher.test(handler.ns))
      && (!fn       || zid(handler.fn) === zid(fn))
      && (!selector || handler.sel == selector)
  })
}

Find the event handle for the element.

event = parse(event)

The parse function is called to separate the event name and namespace of the event parameter.

if (event.ns) var matcher = matcherFor(event.ns)

If a namespace exists, a regular expression matcher matching the namespace is generated.

return (handlers[zid(element)] || []).filter(function(handler) {
    ...
  })

What is returned is actually a qualified handle function in handlers[zid(element)]. Handlers are cached handle containers, using the element's _zid attribute value as the key.

javascript return handler // conditional 1 & & & (! Event. e | handler. e == event. e) / / conditional 2 & (! Event. ns | matcher. test (handler. ns)) / / / conditional 3 & (! FN | Zid (handler. fn) == Zid (fn) / conditional 4 & (! Selector | handler. sel = selector) / conditional 5 & (!

The returned handle must satisfy five conditions:

  1. Handles must exist
  2. If event.e exists, the event name of the handle must be the same as the event name of the event
  3. If a namespace exists, the namespace of the handle must match the namespace of the event (the role of matcherFor s)
  4. If a matching event handle is specified as fn, the _zid of the current handle handler must be consistent with the specified handle FN
  5. If a selector is specified, the selector in the current handle must be the same as the specified selector.

As can be seen from the comparison above, the cached handle object takes the form of:

{
  fn: '', // function
  e: '', // Event name
  ns: '', // Namespace
  sel: '',  // selector
  // In addition to that, there are actually others.
  i: '', // Functional Index
  del: '', // Delegation function
  proxy: '', // Proxy function
  // The following attributes will be discussed
}

realEvent, returns the corresponding bubble event

focusinSupported = 'onfocusin' in window,
focus = { focus: 'focusin', blur: 'focusout' },
hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }
function realEvent(type) {
  return hover[type] || (focusinSupported && focus[type]) || type
}

This function actually converts focus/blur into focus/focus out and mouseenter/mouseleave into mouseover/mouseout events.

Since the support level of the focusin/focusout event browser is not very good, we need to do a detection of the browser support, if the browser supports, then return, otherwise, return the original event name.

compatible, modify event object

returnTrue = function(){return true},
returnFalse = function(){return false},
eventMethods = {
  preventDefault: 'isDefaultPrevented',
  stopImmediatePropagation: 'isImmediatePropagationStopped',
  stopPropagation: 'isPropagationStopped'
}

function compatible(event, source) {
  if (source || !event.isDefaultPrevented) {
    source || (source = event)

    $.each(eventMethods, function(name, predicate) {
      var sourceMethod = source[name]
      event[name] = function(){
        this[predicate] = returnTrue
        return sourceMethod && sourceMethod.apply(source, arguments)
      }
      event[predicate] = returnFalse
    })

    try {
      event.timeStamp || (event.timeStamp = Date.now())
    } catch (ignored) { }

    if (source.defaultPrevented !== undefined ? source.defaultPrevented :
        'returnValue' in source ? source.returnValue === false :
        source.getPreventDefault && source.getPreventDefault())
      event.isDefaultPrevented = returnTrue
      }
  return event
}

The compatible function is used to correct the browser differences of event objects. Several methods are added to event objects, such as isDefaultPrevented, isImmediatePropagationStopped and isPropagationStopped. For browsers that do not support timeStamp, timeStamp attributes are added to event objects.

if (source || !event.isDefaultPrevented) {
  source || (source = event)

  $.each(eventMethods, function(name, predicate) {
    var sourceMethod = source[name]
    event[name] = function(){
      this[predicate] = returnTrue
      return sourceMethod && sourceMethod.apply(source, arguments)
    }
    event[predicate] = returnFalse
  })

The judgement condition is that the original event object exists or the isDefault Prevented of event event does not exist.

If source does not exist, event is assigned to source as the original event object.

Traversing through EvetMethods, we get the corresponding method name of the original event object, sourceMethod.

event[name] = function(){
  this[predicate] = returnTrue
  return sourceMethod && sourceMethod.apply(source, arguments)
}

Rewrite the method corresponding to the event object. If the corresponding method is executed, the new method corresponding to the method in the event is assigned to the return True function. For example, when the preventDefault method is executed, the return value of the isDefaultPrevented method is true.

event[predicate] = returnFalse

This is to initialize the newly added property to the returnFalse method

try {
  event.timeStamp || (event.timeStamp = Date.now())
} catch (ignored) { }

This section adds the timeStamp attribute to browsers that do not support the timeStamp attribute.

if (source.defaultPrevented !== undefined ? source.defaultPrevented :
    'returnValue' in source ? source.returnValue === false :
    source.getPreventDefault && source.getPreventDefault())
  event.isDefaultPrevented = returnTrue
  }

This is compatible with different implementations of browser preventDefault.

source.defaultPrevented !== undefined ? source.defaultPrevented : 'Ternary expression'

If the browser supports defaultPrevented, the defaultPrevented value is returned

'returnValue' in source ? source.returnValue === false : 'The latter judgment'

Return Value defaults to true, and if the browser's default behavior is blocked, return Value becomes false.

source.getPreventDefault && source.getPreventDefault()

If the browser supports the getPreventDefault method, the getPreventDefault() method is called to obtain whether the default behavior of the browser is blocked.

When you judge true, set isDefaultPrevented to the returnTrue method.

createProxy, Create Proxy Objects

ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,
function createProxy(event) {
  var key, proxy = { originalEvent: event }
  for (key in event)
    if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]

    return compatible(proxy, event)
}

In zepto, when an event is triggered, the event returned to us is neither a native event object nor a proxy object, which is the method of creating the proxy object.

ignoreProperties is used to exclude attributes that start with A-Z, that is, all uppercase letters, and non-standard attributes that end with return value, layerX/layerY, webkitMovementX/webkitMovementY.

for (key in event)
  if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]

Traversing the native event object, eliminating undefined attributes and undefined attributes, and copying attributes and values to the proxy object.

The final return is the modified proxy object

eventCapture

function eventCapture(handler, captureSetting) {
  return handler.del &&
    (!focusinSupported && (handler.e in focus)) ||
    !!captureSetting
}

Returning true indicates that the event handle is executed in the capture phase, otherwise it is executed in the bubble phase.

If there is an event proxy and the event is a focus/blur event, when the browser does not support the focus/focus out event, it is set to true, and the event is handled in the capture stage to achieve the purpose of bubbling indirectly.

Otherwise, the customized captureSetting sets the timing of event execution.

add, the Core Method of Event Module

function add(element, events, fn, data, selector, delegator, capture){
  var id = zid(element), set = (handlers[id] || (handlers[id] = []))
  events.split(/\s/).forEach(function(event){
    if (event == 'ready') return $(document).ready(fn)
    var handler   = parse(event)
    handler.fn    = fn
    handler.sel   = selector
    // emulate mouseenter, mouseleave
    if (handler.e in hover) fn = function(e){
      var related = e.relatedTarget
      if (!related || (related !== this && !$.contains(this, related)))
        return handler.fn.apply(this, arguments)
        }
    handler.del   = delegator
    var callback  = delegator || fn
    handler.proxy = function(e){
      e = compatible(e)
      if (e.isImmediatePropagationStopped()) return
      e.data = data
      var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
      if (result === false) e.preventDefault(), e.stopPropagation()
      return result
    }
    handler.i = set.length
    set.push(handler)
    if ('addEventListener' in element)
      element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
      })
}

The add method adds events and event responses to elements. There are many parameters. Let's first look at the meaning of each parameter.

element // Event Binding Elements
events // List of events that need to be bound
fn // Handles for event execution
data // Data passed to the event object when the event is executed
selector // Selector for Event Binding Elements
delegator // Event delegation function 
capture // That stage executes event handles
var id = zid(element), set = (handlers[id] || (handlers[id] = []))

Gets or sets the id, set as the event handle container.

events.split(/\s/).forEach(function(event){})

Handling each event

if (event == 'ready') return $(document).ready(fn)

If it is a read event, the read method is called to abort subsequent execution

var handler   = parse(event)
handler.fn    = fn
handler.sel   = selector
// emulate mouseenter, mouseleave
if (handler.e in hover) fn = function(e){
  var related = e.relatedTarget
  if (!related || (related !== this && !$.contains(this, related)))
    return handler.fn.apply(this, arguments)
    }
handler.del   = delegator
var callback  = delegator || fn

This code sets some properties on handler and caches them.

Here we mainly look at the simulation of mouseenter and mouseleave events. As mentioned above, event handles are executed only when conditions are satisfied.

handler.proxy = function(e){
  e = compatible(e)
  if (e.isImmediatePropagationStopped()) return
  e.data = data
  var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
  if (result === false) e.preventDefault(), e.stopPropagation()
  return result
}

The proxy function of the event handle.

E is the native event object at the time of event execution, so compatible is called to correct e first.

Call the isImmediatePropagationStopped method to see if the stopImmediatePropagation method has been executed, and if it has been executed, terminate the execution of subsequent programs.

Then extend the e object to store data on the data attribute of E.

Execute the event handle, taking the e object as the first parameter of the handle.

If, after execution, an explicit return to false prevents default behavior and event bubbles in the browser.

set.push(handler)
if ('addEventListener' in element)
  element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))

Store the handle in the handle container

The addEventListener method of the element is called to add events. The callback function of the event uses the proxy function of the handle. EvetCapture (handler, capture) specifies whether or not to execute in the capture phase.

remove, delete events

function remove(element, events, fn, selector, capture){
  var id = zid(element)
  ;(events || '').split(/\s/).forEach(function(event){
    findHandlers(element, event, fn, selector).forEach(function(handler){
      delete handlers[id][handler.i]
      if ('removeEventListener' in element)
        element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
        })
  })
}

First, get the _zid of the specified element

;(events || '').split(/\s/).forEach(function(event){})

Traversing events that need to be deleted

findHandlers(element, event, fn, selector).forEach(function(handler){})

Call the findHandlers method to find event handles that need to be deleted under event

delete handlers[id][handler.i]

Delete the corresponding event in the handle container, and the i attribute in the handle object in the add function is used here to find the handle that needs to be deleted.

element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))

Call the removeEventListener method to delete the corresponding event.

Tool function

$.event

$.event = { add: add, remove: remove }

Exposing the add and remove methods should be convenient for third-party plug-ins to extend.

$.proxy

$.proxy = function(fn, context) {
  var args = (2 in arguments) && slice.call(arguments, 2)
  if (isFunction(fn)) {
    var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) }
    proxyFn._zid = zid(fn)
    return proxyFn
  } else if (isString(context)) {
    if (args) {
      args.unshift(fn[context], fn)
      return $.proxy.apply(null, args)
    } else {
      return $.proxy(fn[context], fn)
    }
  } else {
    throw new TypeError("expected function")
  }
}

The proxy function, which acts a little like the bind method in JS, returns a function that changes the execution context after the proxy.

var args = (2 in arguments) && slice.call(arguments, 2)

If more than three parameters are provided, the first two parameters are removed and the latter parameters are taken as the parameters of the execution function fn.

if (isFunction(fn)) {
  var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) }
  proxyFn._zid = zid(fn)
  return proxyFn
}

The execution function of proxy can be passed in two ways. One is that the first parameter is passed in directly, the other is that the first parameter is a context object, and the execution function is also passed in together in the context object.

In this paper, we determine whether FN is a function, that is, the first way of parameter transfer. We call the apply method of FN function, and take context as the first parameter of apply. If args exists, we merge it with the parameters of fn.

Add the _zid attribute to the proxy function to facilitate the search of the function.

else if (isString(context)) {
  if (args) {
    args.unshift(fn[context], fn)
    return $.proxy.apply(null, args)
  } else {
    return $.proxy(fn[context], fn)
  }

If the function is already included in the context object, that is, the first parameter fn is the object, and the second parameter context is the string, which is used to specify the name of the property of the executing function in the context object.

if (args) {
  args.unshift(fn[context], fn)
  return $.proxy.apply(null, args)
}

If the parameter exists, the fn[context], that is, the execution function and fn, that is, the context object, are placed at the beginning of the args array, so that the parameter is modified to be the same as the first method of parameter transfer, and then the $. proxy function is called. Apply method is called here because you don't know how many parameters there are, so calling apply can be passed in as an array.

If args does not exist, only two parameters are determined, so the $. proxy method can be called directly.

$.Event

specialEvents={},
specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'

$.Event = function(type, props) {
  if (!isString(type)) props = type, type = props.type
  var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
  if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
  event.initEvent(type, bubbles, true)
  return compatible(event)
}

Special Events is a modification of mouse events to MouseEvents, which should deal with browser compatibility issues. In some browsers, the event type of these events is not MouseEvents.

The $. Event method is used to create a specific type of event manually.

The parameter type can be either a string or an event object. props are objects that extend event objects.

if (!isString(type)) props = type, type = props.type

If it is not a string, that is, an event object, type is assigned to props, and type is the type attribute value in the current event object.

var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true

Call the createEvent method to create event events of the corresponding type and set the event bubbles to true by default

if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])

Traversing props attributes, if there are specified bubbles, then using the specified bubble behavior, other attributes are copied to the event object to achieve the expansion of the event object.

event.initEvent(type, bubbles, true)
return compatible(event)

Initialize the newly created event and return the corrected event object.

Method

.on()

$.fn.on = function(event, selector, data, callback, one){
  var autoRemove, delegator, $this = this
  if (event && !isString(event)) {
    $.each(event, function(type, fn){
      $this.on(type, selector, data, fn, one)
    })
    return $this
  }

  if (!isString(selector) && !isFunction(callback) && callback !== false)
    callback = data, data = selector, selector = undefined
    if (callback === undefined || data === false)
      callback = data, data = undefined

      if (callback === false) callback = returnFalse

      return $this.each(function(_, element){
        if (one) autoRemove = function(e){
          remove(element, e.type, callback)
          return callback.apply(this, arguments)
        }

        if (selector) delegator = function(e){
          var evt, match = $(e.target).closest(selector, element).get(0)
          if (match && match !== element) {
            evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
            return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
          }
        }

        add(element, event, callback, data, selector, delegator || autoRemove)
      })
}

The on method binds events to elements, and finally calls the add method. The previous section of logic mainly modifies parameters.

var autoRemove, delegator, $this = this
if (event && !isString(event)) {
  $.each(event, function(type, fn){
    $this.on(type, selector, data, fn, one)
  })
  return $this
}

autoRemove represents a function that is automatically unbound after the event response is executed.

Event can be a string or an object. When it is an object, the attribute of the object is an event type and the attribute value is a handle.

This section deals with the case when event is the object, traverses the object, gets the event type and handle, and then calls on method again to continue to modify the subsequent parameters.

if (!isString(selector) && !isFunction(callback) && callback !== false)
  callback = data, data = selector, selector = undefined
if (callback === undefined || data === false)
  callback = data, data = undefined

if (callback === false) callback = returnFalse

Let's first analyze the case when the first if, selector is not a string, callback is not a function, and callback is not a false.

Here you can confirm that the selector is not passed, because the selector is not a required parameter.

So here we assign data to callback, selector to data, and selector to undefined, because selector is not passed, so the position of the corresponding parameters is moved forward one bit.

Looking at the second if, if the callback (original data) is undefined and the data is false, it means that the selector has not been passed and the data has not been passed, so the data is assigned to the callback, and the data is set to undefined, i.e., the parameter is moved forward one more place.

The third if, if callback === false, replaces it with the return False function, and if not, it will report an error.

return $this.each(function(_, element){
  add(element, event, callback, data, selector, delegator || autoRemove)
})

As you can see, here is a collection of traversing elements, calling the add method for each element and binding events.

if (one) autoRemove = function(e){
  remove(element, e.type, callback)
  return callback.apply(this, arguments)
}

If you call it only once, set autoRemove as a function that calls the remove method before the handle executes and unbinds the corresponding event bound to the element.

if (selector) delegator = function(e){
  var evt, match = $(e.target).closest(selector, element).get(0)
  if (match && match !== element) {
    evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
    return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
  }
}

If a selector exists, it means that an event agent needs to be done.

Call the closest method, start looking up from the target element e.target of the event, and return the first element that matches the selector. For the closest method, see< Reading Zepto Source Collection Element Search > Analysis.

If match exists and match is not the current element, the createProxy method is called to create the proxy object for the current event object, and then the $. extend method is called to extend the current Target and live Fired attributes for the proxy object, saving the proxy element and the element triggering the event into the event object.

Finally, the handle function is executed, and the proxy element match is used as the context of the handle. The first parameter of the original handle function is replaced by the proxy event object evt.

This function is assigned to the delegator and passed to the add method as a proxy function.

.off()

$.fn.off = function(event, selector, callback){
  var $this = this
  if (event && !isString(event)) {
    $.each(event, function(type, fn){
      $this.off(type, selector, fn)
    })
    return $this
  }

  if (!isString(selector) && !isFunction(callback) && callback !== false)
    callback = selector, selector = undefined

    if (callback === false) callback = returnFalse

    return $this.each(function(){
      remove(this, event, callback, selector)
    })
}

Unbundling events

if (event && !isString(event)) {
  $.each(event, function(type, fn){
    $this.off(type, selector, fn)
  })
  return $this
}

This logic is similar to the on method, and the parameters are modified without further elaboration.

if (!isString(selector) && !isFunction(callback) && callback !== false)
  callback = selector, selector = undefined
if (callback === false) callback = returnFalse

The first if handles the case where the selector parameter is not passed, and the selector location is actually called back.

The second if is to determine if the callback is false, assign the callback to the return False function.

return $this.each(function(){
  remove(this, event, callback, selector)
})

Finally, all elements are traversed, and the remove function is called to unbind events for each element.

.bind()

$.fn.bind = function(event, data, callback){
  return this.on(event, data, callback)
}

The inner call of the bind method is actually the on method.

.unbind()

$.fn.unbind = function(event, callback){
  return this.off(event, callback)
}

The unbind method calls the off method internally.

.one()

$.fn.one = function(event, selector, data, callback){
  return this.on(event, selector, data, callback, 1)
}

The on method is also called inside the one method, except that the one parameter is passed by default, indicating that the bound event is only executed once.

.delegate()

$.fn.delegate = function(selector, event, callback){
  return this.on(event, selector, callback)
}

Event delegation is also called on method, but selector must be passed.

.undelegate()

$.fn.undelegate = function(selector, event, callback){
  return this.off(event, selector, callback)
}

To cancel the event delegation, the off method is called internally, and the selector must be passed.

.live()

$.fn.live = function(event, callback){
  $(document.body).delegate(this.selector, event, callback)
  return this
}

A dynamically created node can also respond to events. Events are actually bound to the body and delegated to the current node. The delegate method is called internally.

.die()

$.fn.die = function(event, callback){
  $(document.body).undelegate(this.selector, event, callback)
  return this
}

The event bound to the body by live is destroyed, and the undelegate method is called internally.

.triggerHandler()

$.fn.triggerHandler = function(event, args){
  var e, result
  this.each(function(i, element){
    e = createProxy(isString(event) ? $.Event(event) : event)
    e._args = args
    e.target = element
    $.each(findHandlers(element, event.type || event), function(i, handler){
      result = handler.proxy(e)
      if (e.isImmediatePropagationStopped()) return false
        })
  })
  return result
}

The event callback function is triggered directly.

The parameter event can be either an event type string or an event object.

javascript e = createProxy(isString(event) ? $.Event(event) : event)

If event is a string, call the $. Event tool function to initialize an event object, and then call createProxy to create an event proxy object.

$.each(findHandlers(element, event.type || event), function(i, handler){
  result = handler.proxy(e)
  if (e.isImmediatePropagationStopped()) return false
    })

Call the findHandlers method to find all the handles of the event, call the proxy method, that is, the callback function that is really bound to the event (see the explanation of add), get the result returned by the method, and check whether the result returned by isImmediatePropagationStopped is true, and if so, stop the subsequent execution immediately.

If the result returned is false, subsequent execution is immediately aborted.

Since triggerHandler directly triggers the callback function, the event does not bubble.

.trigger()

$.fn.trigger = function(event, args){
  event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
  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
    else if ('dispatchEvent' in this) this.dispatchEvent(event)
    else $(this).triggerHandler(event, args)
      })
}

Manually trigger events.

event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)

Events can pass event types, objects, and event objects.

If you pass a string or a pure object, call the $. Event method to initialize the event first, otherwise call the compatible method to modify the event object. Because the $. Event method has actually called the compatible method to modify the event object internally, there is no need to call it again externally.

if (event.type in focus && typeof this[event.type] == "function") this[event.type]()

If it's the focus/blur method, call this.focus() or this.blur() methods directly, which are naturally supported by browsers.

If this is a DOM element, that is, there is a dispatchEvent method, then dispatchEvent is used to trigger events. For dispatchEvent, you can refer to it. MDN: EventTarget.dispatchEvent().

Otherwise, call the triggerHandler method directly to trigger the callback function of the event.

Because triggers execute event handles by triggering events, events bubbles.

Series of articles

  1. Code structure for reading Zepto source code
  2. Internal method of reading Zepto source code
  3. Tool functions for reading Zepto source code
  4. Reading the Magic of Zepto Source Code$
  5. Collection operations for reading Zepto source code
  6. Reading Zepto Source Collection Element Search
  7. DOM for reading Zepto source code
  8. Style operations for reading Zepto source code
  9. Property operations for reading Zepto source code

Reference resources

License

Posted by aconway on Wed, 12 Jun 2019 14:01:51 -0700