jQuery Events from the inside out

    The article was written as part of a contest among students of Mail.ru Technopark .
    image

    I think the jQuery JavaSript-library doesn’t need to be introduced, but just in case, I’ll remind you that jQuery is designed to speed up development, provide syntactic sugar for native js and save developers from cross-platform problems.
    Before talking about how event handling in jQuery works, one cannot help but mention the history of event handling in the browser.

    Event handling on js
    Let's recall the history of browsers. They were already far 90s; Internet Explorer was not yet the most common browser, but a ball of Netscape Navigator rules. It was the developers of Navigator who proposed the first model for processing events on js (currently this model is most often called the DOM Level 0 Event Model).

    DOM Level 0 Event Model

    This model is characterized by the following main factors:
    • The reference to the handler function is written directly to the property of the dom object. The names of all events are prefixed with “on” - onclick, onload, etc.
    • All event parameters fall into the handler in the form of an Event Object as the first argument in all browsers except IE. In it, the parameters are in window.event.
    • Events can rise from the node in which they occur to the parent, then to the parent of the parent, and so on. This process is usually called the ascent phase.
    • It is not possible to set multiple handlers for the same event on an element.

    The handler function can be assigned to the property of the DOM element both in the js script and directly in the HTML markup:
    ScriptHTML
    var element = document.getElementById('id');
    element.onmousemove = function (e) { /* … */ };
    
    ...
    ...
    

    It is worth noting that, although this model was not standardized by W3C, it is supported by all modern browsers (at least on the desktop). And this model is still used. Examples (HTML) are taken from yandex.ru and vk.com.
    The model is simple, like three pennies, but life does not stand still ...

    DOM Level 2 Event Model

    image

    In 2000, the W3C released the DOM Level 2 Event Model specification , which can be described as follows:
    • setting the handler using the addEventListener method (removal using removeEventListener);
    • the on prefix is ​​not used in event names;
    • Event Object is similar to DOM Level 0 Event Model;
    • unlimited number of listeners of the same event on the element;
    • ascent phase from DOM Level 0 Event Model;
    • The capture phase preceding the ascent is added, in which the event descends from the root element of the DOM tree down to the element in which the event occurred.

    The registration method for processing has the following syntax addEventListener (eventType, listener, [useCapture = true]):
    • eventType - type of event ('click', 'change', etc.);
    • listener - a reference to a handler function;
    • useCapture is a boolean variable that determines which phase we are subscribing to (true - capture, false - surfacing).

    Then the subscription for resizing the browser window has the form:
    window.addEventListener('resize', function (event) {
      /* … */
    });
    


    Internet Explorer Event Model

    Developers from Microsoft have always gone their own way and until IE 9 versions did not support the generally accepted model of events, but they had their own, with blackjack atachEvent and detachEvent.
    This model is similar to the DOM Level 2 Event Model, but has a number of differences (there are many others, but these are the most basic):
    • attachEvent and detachEvent methods for installing and removing handlers, respectively;
    • 'on' prefix in event names;
    • lack of capture phase.


    Total

    Considering differences between browsers is painfully painful, but not necessary! Do not forget that all of these problems have been encountered before us - in particular, which is why the jQuery library appeared.

    Event handling with jQuery


    Hereinafter, by jQuery we mean jQuery 1.10.2, the current version from the 1.0 branch.
    When we use jQuery, we can safely forget about the differences between addEventListener and attachEvent and much more, because the library provides the following for the developer:
    • Unified method of registering event handlers (using methods);
    • unlimited number of handlers of the same event on one element;
    • passing a normalized Event Object to the handler.

    So, in jQuery there are many methods with which you can subscribe to event processing:
    • bind - installs the handler directly on element (s). It takes the event name and callback as arguments;
    • click , blur , scroll, and many other shortcut methods are similar to calling bind, only the type of the event is the name of the method itself;
    • on - the main method, which allows you to bind the handler directly to the element and delegate the processing of events; for delegation, you must pass an optional selector parameter;
    • delegate - alias for the on method with a modified set of arguments;
    • one is the same as the on method, but the handler will only work the first time the event occurs.

    There are three ways to unsubscribe from an event: unbind (for bind, click and the like), undelegate and off .
    But…
    image

    jQuery is not only an abstraction layer for us from addEventListener and attachEvent, and not only normalizes an Event Object.
    Under the hood of jQuery is an extensive layer of code consisting of:
    • Observer design pattern that implements the logic of centralized installation / removal of event handlers;
    • hooks and parameter filtering systems for Event Object;
    • as well as the possibility of expanding the functionality of jQuery.event using the Special Event API .

    First things first.

    jQuery.event


    Already many people sorted out how jQuery works from the inside (for example, here ), but for some reason the event processing mechanism was bypassed. So let's fill this gap.
    Event processing from the point of view of the library user goes through three stages:
    1. setting an event handler - calling the bind method, etc.
    2. event processing - all the magic that jQuery previously does until it passes the normalized Event Object to “our” handler;
    3. removing an event handler - calling the unbind method, etc.


    Special Event API

    The jQuery library provides third-party code with the ability to influence the process of event handling. In the bowels of the library is the jQuery.event.special object; its keys are the names of events in the processing of which we want to intervene, and the value may be an object with the following properties:
    • noBubble - a flag that determines whether the event should pop up when the trigger method is called. The default is false (event pops up). In the library itself, it is used for the load event , so that the load event in the pictures does not pop up to window.
    • bindType, delegateType (striing) allows you to change the type of event being processed. For example, with this, jQuery implements mouseenter / mouseleave events through standard mouseover / mouseout.
    • setup - the function is called when a handler of this type is installed for the first time on an element.
    • teardown is called when the last handler of this type is removed from the element.
    • add - the function is called whenever a handler is added.
    • remove is called whenever a handler is removed.
    • handle will be called whenever an event occurs instead of the handler passed to the on method (or bind, one, etc.). The use of this special method is well presented on the official page .

    It is possible both to intervene in the mechanism for processing existing events, and to create your own. This is how you can create a pushy event that actually responds to a standard click:
    jQuery.event.special.pushy = {
       bindType: "click",
       delegateType: "click"
    };
    

    In addition to the above, there are other possible properties, the purpose of which can be found on the corresponding page .
    Later in the article, it will be shown at what moments jQuery transfers control to certain special methods, and how exactly it is possible to influence the course of event processing.

    Handler Installation

    image

    Let's pretend that we execute code
     $('div').click(function (e) { /* ... */ });
    along with jQuery. Let’s see what happens at each stage.
    External handler . In order to process the event, you need to create a handler, get a link to it and pass it to any jQuery method responsible for setting the handler. Which we actually did by creating a function using fucntion (e) {/ * ... * /} and calling the click method with it as a parameter.
    Bind, one, delegate methods, etc. Our handler falls into one of these methods, inside of which with a different set of parameters the on method is actually .
    The on method consists of a solid set of if-else blocks - there is a processing and ordering of all possible options for the parameters with which it can be called. Also, it is the on method that implements the installation logiconce a handler is triggered (one method). Finally, the each method is called for the current jQuery object. Each
    method is part of jQuery core, it is with this method that the library iterates over the “jQuery set” (see here ). Each method can bypass arrays and objects depending on the interface provided. If the object provides the length property, then the iteration occurs in the same way as in an array, which is a great example of microoptimization. So, for each DOM element from the jQuery set, the add method is called. All subsequent processing occurs in the add method (from the jQuery.event object). At the beginning, each external handler is assigned a unique identifier
    . For this (and not only) in jQuery there is a global counter jQuery.guid, which increments with each use.
    Then a function is created, which hereinafter we will call the main handler , which receives an Event Object and calls the dispatch method.
    If the handler of this event is set for the first time for this element, a queue of handlers is created (all external handlers will be written to it) and special.setup is called if it is set for this type of event.
    Inside special.setup, a developer can implement his logic of creating a main handler and subscribing to an event using the addEventListener or attachEvent functions, or some other independent logic.
    Next, the created main handler is subscribed to the desired event using the addEventListener or atachEvent methods, depending on the browser, but this only happens if special.setup is not set or returns false.
    Then control is passed to special.add , if specified. Unlike special.setup, special.add is executed whenever a handler is installed via jQuery.
    And after all this, the external handler passed at the very top gets into the handler queue ( link ) and will be called when the event occurs. About it further.

    Event handling

    image

    event occurs - an event occurs in the DOM element and falls into the main handler created by the library (it is subscribed to the event using addEventListener or attachEvent), in which the dispatch method is called.
    In the dispatch method, the event parameter gets the original non-normalized Event Object, which is passed to the fix method for normalization.
    Inside the fix method, it is checked whether the Event Object is ready (if normalization has not been done before), and if not, then:
    • It checks to see if there are fixHooks [type] , where type is the type of event that occurred. fixHooks [type] is an object that can have two properties:
      • props - an array of property names that must be copied from the original Event Object to normalized;
      • filter - a function that normalizes (converts) the parameters of an event.
    • If fixHooks of a certain type are, then this object is used; if not, then with the help of special regexp it is checked whether our event is key-event or mouse-event (each of these types has its own fixHooks - keyHooks and mouseHooks, respectively).
    • Then an “empty” normalized Event Object is created (using new jQuery.Event ), and all properties whose names are present in the jQuery.event.props and fixHooks.props arrays are copied from the original Event Object to the normalized one. At the end of the fix method, the filter function is called , if defined, and the normalized Event Object is returned to dispatch.

    Then special.preDispatch is called , depending on its result, further processing of the event may be completed (if preDispatch returns false).
    After that, for each handler from the queue that was created at the installation stage, special.handler is called , if any. If it is not, then the handler is called directly by the external handler ( link ).
    At the end of the dispatch method, after all handlers are triggered, special.postDispatch is called . The special.postDispatch method can also be defined in your code, like other special methods.

    Delete handler

    Removing the handler goes through stages similar to the stages from the installation, namely: the removal, starting, for example, with unbind , somehow falls into the off method , then jQuery iterates over the set using each , and in the end, the add method is not called, but remove method (thanks, Cap).
    The remove method (from jQuery.event) does the opposite of the add method:
    • Searches for the external handler in the queue of processors by identifier. Then the external handler is removed from the queue.
    • If the queue is empty, then it is deleted, as well as the main handler using jQuery.removeEvent , which, in turn, is also a wrapper over removeEventListener and detachEvent.

    You can influence the process of removing a handler by defining the functions special.remove and special.teardown . remove is called whenever teardown is called when the queue is empty.

    Total


    We remembered how events were processed in the browser, what happened to the client js-code when entering the jQuery arena, and what happens inside this library. A single main handler for the element, normalization of the Event Object through copying and filtering, a queue of event handlers and a zoo of installation methods - this is the implementation of the jQuery that presented us with the Observer pattern.
    At least such important topics for Events as delegation, preventDefault (), stopPropagation (), trigger and namespaces were left behind. But only source can tell everything. So github is waiting. :)

    Also popular now: