πŸ“œ ⬆️ ⬇️

Implementation, analog and adaptation for β€œpure” javascript JavaScript jQuery () functions; and adjacent to it

Once jQuery library was a good helper for programmers. Since it allowed at times to facilitate the creation of functionality, which at that time with the help of a single JavaScript was almost impossible to write, and provided a very good cross-browser compatibility.

Now JavaScript has received a lot of big updates, functions in which have received a large number of polyfills and can replace jQuery functions. But, unfortunately, JS has not yet learned how to do most of the functions that are present in jQuery.

In this article I will tell you about the most important jQuery function - jQuery(); .
')
JQuery(); function JQuery(); consists of two functions - the main and the so-called cover. You start the cover, the cover starts the main function and returns the result from it.

The cover looks like this:

 var jQuery = function (selector, context) { return new jQuery.prototype.init(selector, context); }; 

As you can see from the code, when we run the jQuery(); function jQuery(); it runs the init function with the same parameters from its prototype. The init function is the main one.

Next, we should create a prototype for our function, in the constructor of which we will refer to the jQuery function. Note: in older jQuery versions, the constructor parameter is not specified.

 jQuery.prototype = { constructor: jQuery }; 

Where did the jQuery.fn property come from and why can we specify new functions for jQuery objects through it?
The fact is that the developers equated the jQuery.prototype object and jQuery.fn , thereby setting two parameters for one object in order to be able to add new functions for objects more briefly.

 jQuery.fn = jQuery.prototype = { constructor: jQuery }; 


Now let's start creating the main function. I will not describe the full structure of the function, since It will take at least a week for me, and I'll write how a shorter version is implemented, following the example of jQuery.

In jQuery, the entire function consists of conditions that iterate over the data obtained by the function, and an array into which the necessary elements are thrown.

The initial view of the function looks like this:

 var init = jQuery.prototype.init = function (selector, context) { /**    ,       */ var array = []; /**    , *     *  HTML     */ if (typeof selector === 'string') { /**  ,      DOM  */ } else if (selector.nodeType) { /** *      *      */ } else if ({}.toString.call(selector) === '[object Array]') { /** *    , *      */ } else if ({}.toString.call(selector) === '[object Object]') { /**  ,     ? */ } else if ({}.toString.call(selector) === '[object HTMLCollection]' || {}.toString.call(selector) === '[object NodeList]') { /**    β€”   */ } else if ({}.toString.call(selector) === '[object Function]') { /**       ,     */ } else { return array; } }; 

Consider the first condition - if the selector is a string. If this is a string, then we should check: this is a selector or HTML code (after all, jQuery can parse HTML code in this way).

In jQuery, to check whether the first argument is HTML code, a regular expression is used to check the first and last characters and the total number of characters, which must be greater than or equal to three.

 /** ,     HTML  */ if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) { /**    HTML,    */ } else { }; 

If the first argument is HTML code, the following actions occur:

  1. Using special functions, the HTML code is parsed and the live array returned by the parsing function is combined with the main array.

    Implementation and analog of the HTML code parsing function - jQuery.parseHTML ()
    Unfortunately, I will not tell you about how this function is implemented, since She has too many branches, about which you can write a separate article. But you do not worry, about how this function works, have already written. You can read this article by clicking on the following link: habrahabr.ru/post/164533

    I will tell you about alternative functions with which you can implement this functionality.

    1. The first alternative is to create an element into which, using innerHTML insert our string with the code and take the already prepared DOM elements.

       var parseHTML = function (HTML) { /** *      , *      *      β€” . *         *       */ if (typeof HTML === 'string' && HTML[0] === "<" && HTML[HTML.length - 1] === ">" && HTML.length >= 3) { /**   */ var E = document.createElement('div'); /**     HTML  */ E.innerHTML = HTML; /**      */ return E.childNodes; } else { /**   β€”  ,     */ return []; }; }; 

    2. The second alternative is to DOMParser(); using the DOMParser(); function DOMParser(); and its additional function parseFromString(); .

       var parseHTML = function (HTML) { /** *      , *      *      β€” . *         *       */ if (typeof HTML === 'string' && HTML[0] === "<" && HTML[HTML.length - 1] === ">" && HTML.length >= 3) { /**   HTML      */ var DOM = new DOMParser().parseFromString(HTML, 'text/html'); /**     */ var DOMList = DOM.body.childNodes; /**      */ return DOMList; } else { /**   β€”  ,     */ return []; }; }; 

    3. The third alternative is to parse using the document.implementation.createHTMLDocument(); function document.implementation.createHTMLDocument(); .

       var parseHTML = function (HTML) { /** *      , *      *      β€” . *         *       */ if (typeof HTML === 'string' && HTML[0] === "<" && HTML[HTML.length - 1] === ">" && HTML.length >= 3) { /**   HTML  */ var DOM = document.implementation.createHTMLDocument(); /**    HTML  */ DOM.body.innerHTML = HTML; /**     */ var DOMList = DOM.body.childNodes; /**    */ return DOMList; } else { /**   β€”  ,     */ return []; }; }; 


    But we need a function that will repeat all the functionality of the jQuery function. Therefore, we will create one function that will include all three of the above options:

     var parseHTML = function (data, context, keepScripts) { /**      ,     */ if (typeof data !== 'string') { return []; } /** *    context *  keepScripts,    *  true/false */ if (typeof context === 'boolean') { keepScripts = context; context = false; }; var DOM, DOMList; if (!context || context === document) { /**  HTML  */ DOM = document.implementation.createHTMLDocument() || new DOMParser().parseFromString('', 'text/html'); /**    HTML  */ DOM.body.innerHTML = data; /**     */ DOMList = DOM.body.childNodes; } else if (context.nodeType) { /**   ,      */ DOM = document.createElement('div'); /**       */ context.appendChild(DOM); /**     */ DOM.innerHTML = data; /**    */ DOMList = DOM.childNodes; /**  DOM */ DOM.parentNode.removeChild(DOM); }; /**    ,    */ if (keepScripts === true) { /**    */ var scripts = DOM.scripts || DOM.querySelectorAll('script'); /**     */ if (scripts.length) { /**    */ for (var i = 0; i < scripts.length; i++) { /**     */ var script = document.createElement('script'); /**       */ script.innerHTML = scripts[i].innerHTML; if (scripts[i].attributes) script.attributes = scripts[i].attributes; /**    HEAD   */ document.head.appendChild(script); /**    HEAD */ script.parentNode.removeChild(script); }; }; }; /**        */ return (function () { var array = []; for (var i = 0; i < DOMList.length; i++) { array[array.length] = DOMList[i]; }; return array; })(); }; 


    Implementation and analog of the function of joining arrays - jQuery.merge ()
    If we look at the jQuery source, we see that they have this function implemented as follows:

     var merge = function( first, second ) { var len = +second.length, /**       second.length  ,   ,     ( - ,   ) */ j = 0, i = first.length; /**     */ for ( ; j < len; j++ ) { /** *       *       *     */ first[ i++ ] = second[ j ]; } /**       */ first.length = i; /**    */ return first; }; 

    There are several alternatives to the jQuery.merge() function:

    1. The first alternative is the Array.concat(); function Array.concat(); which combines arrays.

       [0, 1, 2].concat([3, 4, 5]); // [0, 1, 2, 3, 4, 5] 

      But this function will not work if you want to attach a collection or a Node list to an HTML array, since you will have an array in the array, instead of combining.

      But it is quite easy to fix: you need to convert a living array into a regular one This can be done using the [].slice.call() function.

       [0, 1, 2].concat( [].slice.call(document.querySelectorAll(selector)) ); // [0, 1, 2, element, element] 

      Or it can be done by sorting all the elements of the living array and moving them into a regular array.

       var elements = document.querySelectorAll(selector); var array = []; for (var i = 0; i < elements.length; i++) { array[array.length] = elements[i]; }; [0, 1, 2].concat(array); // [0, 1, 2, element, element] 

    2. The second alternative is to iterate over the elements of the second array and transfer them to the first. The same technology can be observed in the standard jQuery function.

       var merge = function (array0, array1) { for (var i = 0; i < array1.length; i++) { array0[array0.length] = array1[i]; /** *  array0.push(array1[i]); *      */ }; return array0; }; merge([0, 1, 2], [3, 4, 5]); // [0, 1, 3, 4, 5, 6] 



  2. Check whether the context argument is an object with parameters. If yes, then write all the parameters with arguments to the DOM elements.

     if ({}.toString.call(context) === '[object Object]') { for (var i = 0; i < array.length; i++) { for (var argument in context) { array.setAttribute(argument, context[argument]); }; }; }; 

  3. Display the finished array with elements

If the first argument is not HTML code, then it is a selector. In jQuery, selector elements are searched using another library called Sizzle . The advantage of this library is that its search by selector starts working from IE7 +.

The search for elements by a selector using this library is as follows:

 Sizzle( String selector ); 

But I will use its JavaScript alternative - document.querySelectorAll( String selector ); . The only drawback of this method in front of the Sizzle library is that it only works with IE9 +.

Before outputting elements by a selector, we need to check the context argument and make sure that it is not an element or elements in which to search for elements by a selector. I will not paint every step, as I explained all the steps in the code.

 /** * ,    context   *  ,      *     selector */ /**   context   */ if (context && context.nodeType) { /**      */ var Elements = context.querySelectorAll(selector); /**        */ return jQuery.merge(array, Elements); /**   context    */ } else if ({}.toString.call(context) === '[object HTMLCollection]' || {}.toString.call(context) === '[object NodeList]') { /** *      *      */ for (var i = 0; i < context.length; i++) { /**      */ var Elements = context[i].querySelectorAll(selector); /**      */ jQuery.merge(array, Elements); }; /**   */ return array; /**   context   */ } else if (typeof context === 'string') { /**     *   */ var Parents = document.querySelectorAll(context); /** *      *      */ for (var i = 0; i < Parents.length; i++) { /**      */ var Elements = Parents[i].querySelectorAll(selector); /**      */ jQuery.merge(array, Elements); }; /**   */ return array; /** *       , *      */ } else { /**      */ var Elements = document.querySelectorAll(selector); /**   */ return jQuery.merge(array, Elements); }; 

In jQuery, all these checks are replaced by the search function - .find(); . Therefore, the jQuery(selector, elements); entry jQuery(selector, elements); is an abbreviated function jQuery(elements).find(selector);

Now our main function is as follows:

 var init = jQuery.prototype.init = function (selector, context) { /**    ,       */ var array = []; /**    , *     *  HTML     */ if (typeof selector === 'string') { /** ,     HTML  */ if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) { /**  HTML       */ jQuery.merge(array, jQuery.parseHTML(selector, context && context.nodeType ? context.ownerDocument || context : document, true)); /** ,    context  *  .  ,     *   DOM . */ if ({}.toString.call(context) === '[object Object]') { for (var i = 0; i < array.length; i++) { for (var argument in context) { array.setAttribute(argument, context[argument]); }; }; }; /**   */ return array; /**    HTML,    */ } else { /** * ,    context   *  ,      *     selector */ /**   context   */ if (context && context.nodeType) { /**      */ var Elements = context.querySelectorAll(selector); /**        */ return jQuery.merge(array, Elements); /**   context    */ } else if ({}.toString.call(context) === '[object HTMLCollection]' || {}.toString.call(context) === '[object NodeList]') { /** *      *      */ for (var i = 0; i < context.length; i++) { /**      */ var Elements = context[i].querySelectorAll(selector); /**      */ jQuery.merge(array, Elements); }; /**   */ return array; /**   context   */ } else if (typeof context === 'string') { /**     *   */ var Parents = document.querySelectorAll(context); /** *      *      */ for (var i = 0; i < Parents.length; i++) { /**      */ var Elements = Parents[i].querySelectorAll(selector); /**      */ jQuery.merge(array, Elements); }; /**   */ return array; /** *       , *      */ } else { /**      */ var Elements = document.querySelectorAll(selector); /**   */ return jQuery.merge(array, Elements); } }; /**  ,      DOM  */ } else if (selector.nodeType) { /** *      *      */ } else if ({}.toString.call(selector) === '[object Array]') { /** *    , *      */ } else if ({}.toString.call(selector) === '[object Object]') { /**  ,     ? */ } else if ({}.toString.call(selector) === '[object HTMLCollection]' || {}.toString.call(selector) === '[object NodeList]') { /**    β€”   */ } else if ({}.toString.call(selector) === '[object Function]') { /**       ,     */ } else { return array; } }; 

Now consider the second condition - if the selector is a DOM element. If the selector is a DOM element, then we simply insert this element into the main array, which we then output.

 /**     */ array[0] = selector; /**    */ return array; 

We turn to the third condition - if the selector is an array. If it is an array, then we combine it with the main array, which we then output. We will combine arrays using the above function.

 /**   */ jQuery.merge(array, selector); /**    */ return array; 

Now about the fourth condition - if the selector is an object with parameters. If the selector is an object with parameters, then, as in the version with the DOM element, we simply add the object to the array and output.

 /**     */ array[0] = selector; /**    */ return array; 

Consider the fifth condition - if the selector is a live array. If the selector is a live array, then we transfer elements from it to the main array. Actions look like in the case of an array.

 /**         */ jQuery.merge(array, selector); /**    */ return array; 

It remains to consider the last condition - if the selector is a function. When the selector is a function, then the function is jQuery( Function ); is an abbreviated function jQuery(document).ready( Function ); .

Implementation and analog of the function of starting the code after loading the DOM - jQuery (document) .ready (Function);
When we look at the jQuery source, we’ll see that this function is implemented there as follows:

 var ready = (function() { var readyList, DOMContentLoaded, class2type = {}; class2type["[object Boolean]"] = "boolean"; class2type["[object Number]"] = "number"; class2type["[object String]"] = "string"; class2type["[object Function]"] = "function"; class2type["[object Array]"] = "array"; class2type["[object Date]"] = "date"; class2type["[object RegExp]"] = "regexp"; class2type["[object Object]"] = "object"; var ReadyObj = { //   DOM   ?   true,    . isReady: false, // ,    ,    . readyWait: 1, //  ( )   holdReady: function(hold) { if (hold) { ReadyObj.readyWait++; } else { ReadyObj.ready(true); } }, // ,  DOM  ready: function(wait) { //    ,   DOMready/load     if ((wait === true && !--ReadyObj.readyWait) || (wait !== true && !ReadyObj.isReady)) { // ,  body ,  ,  IE . if (!document.body) { return setTimeout(ReadyObj.ready, 1); } //   DOM  ReadyObj.isReady = true; //    DOM Ready ,   ,  , if (wait !== true && --ReadyObj.readyWait > 0) { return; } //   ,  readyList.resolveWith(document, [ReadyObj]); //     //if ( ReadyObj.fn.trigger ) { // ReadyObj( document ).trigger( "ready" ).unbind( "ready" ); //} } }, bindReady: function() { if (readyList) { return; } readyList = ReadyObj._Deferred(); //  ,  $(document).ready()   //  ,   . if (document.readyState === "complete") { //    ,       return setTimeout(ReadyObj.ready, 1); } // Mozilla, Opera  webkit nightlies       if (document.addEventListener) { //   callback  document.addEventListener("DOMContentLoaded", DOMContentLoaded, false); //   window.onload,     window.addEventListener("load", ReadyObj.ready, false); //     IE } else if (document.attachEvent) { //    , // , ,     iframes document.attachEvent("onreadystatechange", DOMContentLoaded); //   window.onload,     window.attachEvent("onload", ReadyObj.ready); //  IE,   frame //  ,    var toplevel = false; try { toplevel = window.frameElement == null; } catch (e) {} if (document.documentElement.doScroll && toplevel) { doScrollCheck(); } } }, _Deferred: function() { var //  callback callbacks = [], // stored [ context , args ] fired, //   ,     firing, //  ,    cancelled, //  deferred = { // done( f1, f2, ...) done: function() { if (!cancelled) { var args = arguments, i, length, elem, type, _fired; if (fired) { _fired = fired; fired = 0; } for (i = 0, length = args.length; i < length; i++) { elem = args[i]; type = ReadyObj.type(elem); if (type === "array") { deferred.done.apply(deferred, elem); } else if (type === "function") { callbacks.push(elem); } } if (_fired) { deferred.resolveWith(_fired[0], _fired[1]); } } return this; }, //       resolveWith: function(context, args) { if (!cancelled && !fired && !firing) { // ,    args = args || []; firing = 1; try { while (callbacks[0]) { callbacks.shift().apply(context, args); //shifts a callback, and applies it to document } } finally { fired = [context, args]; firing = 0; } } return this; }, //          resolve: function() { deferred.resolveWith(this, arguments); return this; }, //    ? isResolved: function() { return !!(firing || fired); }, //  cancel: function() { cancelled = 1; callbacks = []; return this; } }; return deferred; }, type: function(obj) { return obj == null ? String(obj) : class2type[Object.prototype.toString.call(obj)] || "object"; } } //   DOM  Internet Explorer function doScrollCheck() { if (ReadyObj.isReady) { return; } try { //   IE,      // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch (e) { setTimeout(doScrollCheck, 1); return; } //     ReadyObj.ready(); } //    document ready if (document.addEventListener) { DOMContentLoaded = function() { document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); ReadyObj.ready(); }; } else if (document.attachEvent) { DOMContentLoaded = function() { // ,  body ,  ,  IE  (ticket #5443). if (document.readyState === "complete") { document.detachEvent("onreadystatechange", DOMContentLoaded); ReadyObj.ready(); } }; } function ready(fn) { //  "" ReadyObj.bindReady(); var type = ReadyObj.type(fn); //  callback' readyList.done(fn); // ReadyList   _Deferred() } return ready; })(); 

, , DOM:

  1. β€” DOMContentLoaded document . IE9+.

     document.addEventListener('DOMContentLoaded', function() { //   }, false); 

  2. β€” readystatechange document readyState . , IE4+.

     document.onreadystatechange = function(){ if(document.readyState === 'complete'){ //   } }; 


DOM:

  1. ― <script></script> body . DOM, .

     <body> <div>...</div> <script> //   </script> </body> 

  2. . body .

     function onload() { //   }; 

     <body> ... <script> onload(); </script> </body> 

  3. β€” body onload .

     function myFunc() { //   }; 

     <body onload="myFunc()">...</body> 



β€” readystatechange , .

 /**     DOM  */ document.onreadystatechange = function(){ if(document.readyState === 'complete'){ selector(); }; }; /**     */ return selector; 

, . __proto__ , jQuery . __proto__ IE11+. .

 if (array.__proto__) { array.__proto__ = jQuery.prototype; } else { for (var param in jQuery.prototype) { array[param] = jQuery.prototype[param]; }; }; 

. :

 /**   β€”  */ var jQuery = function (selector, context) { return new jQuery.prototype.init(selector, context); }; /**     */ jQuery.prototype = { constructor: jQuery }; /**    */ var init = jQuery.prototype.init = function (selector, context) { /**    ,       */ var array = []; /** *   proto   *   , *     *   *   jQuery  */ /**  __proto__   *     IE11+ *      */ if (array.__proto__) { array.__proto__ = jQuery.prototype; } else { for (var param in jQuery.prototype) { array[param] = jQuery.prototype[param]; }; }; /**    , *     *  HTML     */ if (typeof selector === 'string') { /** ,     HTML  */ if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) { /**  HTML       */ jQuery.merge(array, jQuery.parseHTML(selector, context && context.nodeType ? context.ownerDocument || context : document, true)); /** ,    context  *  .  ,     *   DOM . */ if ({}.toString.call(context) === '[object Object]') { for (var i = 0; i < array.length; i++) { for (var argument in context) { array.setAttribute(argument, context[argument]); }; }; }; /**   */ return array; /**    HTML,    */ } else { /** * ,    context   *  ,      *     selector */ /**   context   */ if (context && context.nodeType) { /**      */ var Elements = context.querySelectorAll(selector); /**        */ return jQuery.merge(array, Elements); /**   context    */ } else if ({}.toString.call(context) === '[object HTMLCollection]' || {}.toString.call(context) === '[object NodeList]') { /** *      *      */ for (var i = 0; i < context.length; i++) { /**      */ var Elements = context[i].querySelectorAll(selector); /**      */ jQuery.merge(array, Elements); }; /**   */ return array; /**   context   */ } else if (typeof context === 'string') { /**     *   */ var Parents = document.querySelectorAll(context); /** *      *      */ for (var i = 0; i < Parents.length; i++) { /**      */ var Elements = Parents[i].querySelectorAll(selector); /**      */ jQuery.merge(array, Elements); }; /**   */ return array; /** *       , *      */ } else { /**      */ var Elements = document.querySelectorAll(selector); /**   */ return jQuery.merge(array, Elements); } }; /**  ,      DOM  */ } else if (selector.nodeType) { /**     */ array[0] = selector; /**    */ return array; /** *      *      */ } else if ({}.toString.call(selector) === '[object Array]') { /**   */ jQuery.merge(array, selector); /**    */ return array; /** *    , *      */ } else if ({}.toString.call(selector) === '[object Object]') { /**     */ array[0] = selector; /**    */ return array; /**  ,     ? */ } else if ({}.toString.call(selector) === '[object HTMLCollection]' || {}.toString.call(selector) === '[object NodeList]') { /**         */ jQuery.merge(array, selector); /**    */ return array; /**    β€”   */ } else if ({}.toString.call(selector) === '[object Function]') { /**     DOM  */ document.onreadystatechange = function(){ if(document.readyState === 'complete'){ selector(); }; }; /**     */ return selector; /**       ,     */ } else { return array; } }; jQuery.merge = function (first, second) { var len = +second.length, /**       second.length  ,   ,     */ j = 0, i = first.length; /**     */ for (; j < len; j++) { /** *       *       *     */ first[i++] = second[j]; } /**       */ first.length = i; /**    */ return first; }; jQuery.parseHTML = function (data, context, keepScripts) { /**      ,     */ if (typeof data !== 'string') { return []; } /** *    context *  keepScripts,    *  true/false */ if (typeof context === 'boolean') { keepScripts = context; context = false; }; var DOM, DOMList; if (!context || context === document) { /**  HTML  */ DOM = document.implementation.createHTMLDocument() || new DOMParser().parseFromString('', 'text/html'); /**    HTML  */ DOM.body.innerHTML = data; /**     */ DOMList = DOM.body.childNodes; } else if (context.nodeType) { /**   ,      */ DOM = document.createElement('div'); /**       */ context.appendChild(DOM); /**     */ DOM.innerHTML = data; /**    */ DOMList = DOM.childNodes; /**  DOM */ DOM.parentNode.removeChild(DOM); }; /**    ,    */ if (keepScripts === true) { /**    */ var scripts = DOM.scripts || DOM.querySelectorAll('script'); /**     */ if (scripts.length > 0) { /**    */ for (var i = 0; i < scripts.length; i++) { /**     */ var script = document.createElement('script'); /**       */ script.innerHTML = scripts[i].innerHTML; if (scripts[i].attributes) script.attributes = scripts[i].attributes; /**    HEAD   */ document.head.appendChild(script); /**    HEAD */ script.parentNode.removeChild(script); }; }; }; /**        */ return (function () { var array = []; for (var i = 0; i < DOMList.length; i++) { array[array.length] = DOMList[i]; }; return array; })(); }; 

, jQuery .

 jQuery.prototype.myFunction = function () {...}; 

. : jQuery JavaScript :)

Source: https://habr.com/ru/post/335958/


All Articles