Реализация и альтернатива основных JQuery функций на чистом JavaScript

Когда я начинал учить веб-программирование, встретил лучший из всех, по моему мнению, фреймворков — JQuery. В то далёкое время нельзя было представить нормальное программирование без него, так как он мог делать одной строкой то, что делал JavaScript за 95.

В сегодняшнее время, JavaScript очень изменился. В него добавили большой функционал, который сокращает количество кода в разы и делает программирование более удобным. Но даже с этими обновлениями он не может воспроизвести некоторые, даже самые простые, функции из JQuery, и когда мы решаем отказаться от этого фремворка, то чувствует некую трудность из за этого.

Так вот, в этой статье я хочу рассказать о реализациях некоторых функций из JQuery на чистом JavaScript.


  1. $(document).ready( Function ); или $( Function );


    Для тех кто не знает, это функция готовности DOM дерева. Т.е. эта функция запускается, когда DOM страницы был полностью загружен.

    Начиная с IE9+ эту функцию можно заменить с помощью события DOMContentLoaded повешенного на document.

    Пример:
    document.addEventListener('DOMContentLoaded', function() {
       // Ваш скрипт
    }, false);

    Если вам нужна поддержка начиная с IE4+, то можно воспользоваться более старым методом — с помощью события readystatechange повешенного на document и проверкой readyState.

    Пример:
    document.onreadystatechange = function(){
       if(document.readyState === 'complete'){
          // Ваш скрипт
       }
    };

    Если же мы посмотрим в исходники JQuery, то выйдет следующая функция:

    Пример:
    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,
          // Счетчик, чтобы отслеживать количество элементов, ожидающих до начала события. См. #6781
          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)) {
              // Убедитесь, что тело существует, по крайней мере, в случае, если IE наложает (ticket #5443).
              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) {
                    // Убедитесь, что имеются аргументы (#8421)
                    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() {
          // Убедимся, что тело существует, по крайней мере, в случае, если 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;
    })();

    Запуск функции происходить таким образом:

    ready(function() {
       // Ваш скрипт
    });

  2. $.ajax( Object );


    Для тех кто не знает, эта функция выполняет асинхронный HTTP (Ajax) запрос.

    Как бы это ни было банально, но альтернативой для Jquery.ajax() является XMLHttpRequest

    Немного об использовании:

    Для начала, нам бы следовало создать кроссбраузерную функцию для отправки, т.к. в разных браузерах функция отправки может быть разной. Она создаётся таким образом:

    function getXmlHttp(){
      var xmlhttp;
      try {
        xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
      } catch (e) {
        try {
          xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
        } catch (E) {
          xmlhttp = false;
        }
      }
      if (!xmlhttp && typeof XMLHttpRequest !== 'undefined') {
        xmlhttp = new XMLHttpRequest();
      }
      return xmlhttp;
    }

    А вот сам пример стандартного POST запроса с обработкой ошибок:

    var xmlhttp = getXmlHttp(); // Получаем нашу функцию
    xmlhttp.open('POST', '/someurl', true); // Отправляем POST запрос на адрес "/someurl" 
    
    // Вызываем функцию при изменении статуса запроса
    xmlhttp.onreadystatechange = function(){
      if (xmlhttp.readyState !== 4) return; // Если запрос не завершён, то ничего не делаем
      // Немного о статусах:
      // Статус 0 — Объект XMLHttpRequest был создан, но метод open() ещё не вызывался.
      // Статус 1 — Был вызван метод open(). На этом этапе методом setRequestHeader() могут быть установлены заголовки запроса (request headers), после чего, для начала выполнения запроса, может быть вызван метод send() .
      // Статус 2 — Был вызван метод send() и получены заголовки ответа (response headers) .
      // Статус 3 — Получена часть ответа. Если responseType это пустая строка или имеет значение "text", responseText будет содержать загруженную порцию текста ответа.
      // Статус 4 — Операция доставки данных завершена. Это может означать как то, что передача данных полностью завершена успешно, так и то, что произошла ошибка.
      clearTimeout(timeout); // Удаляем Timeout, если запрос завершён
    
      if (xmlhttp.status == 200) {
          // Если запрос был отправлен успешно и мы получили ответ, то обрабатываем информацию
          console.log(xmlhttp.responseText);
      } else {
          // Если же у нас ошибка, то отправляем её в обработчик
          handleError(xmlhttp.statusText);
      }
    }
    
    // Указываем данные, которые нам нужно отправить
    xmlhttp.send("a=5&b=4");
    // Создаём таймер на 10 секунд. Он нужен для того, чтобы, когда ответ от сервера не приходит, выдать ошибку
    var timeout = setTimeout( function(){ xmlhttp.abort(); handleError('Time over') }, 10000);
    
    // Создаём функцию обработки ошибок
    function handleError(message) {
      // Тут мы принимает текст ошибки и распологаем ним как хотим
      console.log('Ошибка: ' + message);
    }

    По такой же технологией сделана функция JQuery AJAX.
  3. Глобальная функция $(...);


    Если кто не знает, этой функцией создаётся глобальный JQuery объект.

    Тут я не буду расписывать полный функционал этой функции, так как это займёт у меня минимум неделю, а просто напишу, как создаётся подобная вещь по примеру JQuery.

    Для начала создадим обычную функцию, к примеру, Library с аргументами (селектор и контекст).

    var Library = function (selector, context) {};

    Далее пишем общую функцию. В JQuery значения, которые попадают в функцию перебираются с помощью условий и выводится результат

    var init = function (selector, context) {
        // Для начала мы создадим массив, 
        // в который будем скидывать элементы
        var array = [];
    
        /** Сначала мы проработаем вариан,
         * когда кодер указал селектор 
         * или HTML код в первом аргументе
         */
        if (typeof selector === 'string' ) {
            /** Нам нужно попарсить HTML код
             * чтобы узнать селектор это или код.
             * Для парсинка в JQuery используется
             * следующее регулярное выражение:
             * /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/
             */
            if (/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/.exec(selector)) {
                // Сначала я распарсю код
                var DOM = new DOMParser().parseFromString(selector, 'text/html');
                var DOMList = DOM.body.childNodes;
    
                // Тут ещё нужно вспомнить про context и проверить его на значения
                if (!context || {}.toString.call(context) !== '[object Object]') {
                    context = null;
                };
    
                // Далее добавляем элементы новый в массив
                for (var i = 0; i < DOMList.length; i++) {
                    if (context) {
                        for (var attr in context) {
                            DOMList[i].setAttribute(attr, context + '');
                        };
                    };
                    
                    array[array.length] = DOMList[i];
                };
    
                return array;
            } else {
                // Тут нужно проверить
                // является ли свойство
                // context элементом,
                // в котором нужно искать
                // объект
                var DOMList = {}.toString.call(context) === '[object HTMLElement]' ? context.querySelectorAll(selector) : document.querySelectorAll(selector);
    
                // Теперь перекидываем все элементы в массив
                // и выводим
                for (var i = 0; i < DOMList.length; i++) {
                    array[array.length] = DOMList[i];
                };
    
                return array;
            };
        // Тут мы проверим, является ли первый аргумент массивом
        } else if ({}.toString.call(selector) === '[object Array]') {
            // Перекидываем значения и выводим
            for (var i = 0; i < selector.length; i++) {
                array[array.length] = selector[i];
            };
    
            return array;
        // Далее я проверю, может это объект или один элемент
        } else if ({}.toString.call(selector) === '[object Object]' || {}.toString.call(selector) === '[object HTMLElement]') {
            // Запихиваем объект в массив и выводим
            array[0] = selector;
    
            return array;
        // Теперь проверяем, может это живой массив элементов?
        } else if ({}.toString.call(selector) === '[object HTMLCollection]' || {}.toString.call(selector) === '[object NodeList]') {
            // Перекидываем значения и выводим
            for (var i = 0; i < selector.length; i++) {
                array[array.length] = selector[i];
            };
    
            return array;
        // Если ничего не подходит, то выводим пустой массив
        } else {
            return array;
        }
    };

    Теперь мы можем добавить запуск этой функции через основную

    var Library = function (selector, context) {
        // Получаем массив из основной функции
        var array = new init(selector, context);
        /** Тут мы создаём объект.
         * К его proto присваиваем
         * прототип главной функции,
         * чтобы потом можно было 
         * создавать дополнительный
         * функционал
         */
        var object = {
            __proto__: Library.prototype
        };
    
        // Далее мы перекидываем элементы
        // Из массива в объект и создаём
        // параметр length, чтобы потом можно
        // было узнать, сколько элементов
        // в объекте
        for (var i = 0; i < array.length; i++) {
            object[i] = array[i];
        };
        object.length = array.length;
    
        return object;
    };

    Вот и готово. Теперь мы можем получить массив элементов через функцию Library(...); и создавать дополнительный функционал через такую конструкцию Library.prototype.myFunction = function () {...};

Пока всё. Через время я буду опубликовывать статьи про функции JQuery более конкретно
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 29
  • –1
    класс, напишите, пожалуйста
    лайки, к сожалению, тут только по разнарядке :)
    • +2

      Статья в стиле ретро )

      • +2
        $(document).ready( Function )

        реализуется вообще без единой строчки JS — перемещением скрипта в конец документа, перед </body>:)

        • 0
          Про все методы я распишу в своих следующих статьях.
          • 0

            аттрибут defer, например

          • +1
            Как бы это ни было банально, но альтернативой для Jquery.ajax() является XMLHttpRequest

            Стоит добавить, что в перспективе (а с полифилом уже сейчас) удобнее будет использовать fetch.
            • +1
              Спасибо, я забыл про него. В своих следующих статьях я расскажу про неё.
              • 0

                Ну, а для тех, кто не хочет по каким-то причинам использовать fetch, есть axios.

                • 0
                  fetch вроде как невозможно отменить.

                  Это иногда бывает неудобно для SPA. Отправили запрос, но тут пользователь перешел на другой экран и ответ от предыдущего запроса нас уже не интересует. Но ответ все равно придет и выполнится какой-то обработчик.
                  • 0

                    jquery — фреймворк? И не абы какой, а самый лучший? Очень смелое заявление.


                    What is jQuery?
                    jQuery is a fast, small, and feature-rich JavaScript library.
                    • 0
                      window.onload != $(document).ready! onload сработает только после того, как прогрузится весь контент, включая изображения
                      • 0
                        Извините, исправил эту грубую ошибку
                      • –1
                        Было бы интересно для средств разработки реализовать one-click converter для подобных случаев, т.е. в настройках для рефакторинга говорим, предлагай заменять X на Y и описываешь правила конвертирования, в данном случае функции jquery на pure-js.

                        При редактировании кода нажимаем кнопочку, робот пытается что-то сделать за разработчика и предлагает вариант(ы). Понятно, что сразу идеально не получиться, но дорогу осилит идущий.
                        • 0

                          Не думаю, что в этом есть смысл, поскольку уже давно можно писать на самом новом стандарте EcmaScript, используя Babel. Причем сборщик может быть на любой вкус: Grunt, Gulp, Webpack. Только найти нужные пакеты под них и всё.

                        • +1
                          youmightnotneedjquery.com вдуг, кто не знал…
                        • +3
                          В то время, как корабли бороздят просторы ES 2017, тут такое.
                          • 0
                            К сожалению Edge до сих пор не может ES 2016 потянуть :)
                            • +1
                              А babel уже торт?
                              А про Edge не правда.
                              • –1
                                Ну это про новый… У меня в университете до сих пор IE10 стоит
                                • +2

                                  Так вы можете писать на самом новом JS и транспайлить его в «старый», который поймут все браузеры.

                          • +7
                            Право слово, надоели уже с этой библиотекой jQuery.
                            Это просто библиотека, которая содержит набор различных методов, и исключительно ваше право какие из них использовать, какие нет, а какие реализовывать нативно.
                            В той или иной степени различные аналогичные библиотеки (фреймворки) также реализуют одни и те же методы, что и в jQuery, полностью или частично или более расширенно.
                            Ранее использовали jQuery потому что:
                            — можно сделать короче и понятнее код,
                            — это было модно.
                            Сейчас, в основной массе, используют другие принципы построения структуры веб -приложения, — сайта, — ..., и, соответственно, применяют другие библиотеки (фреймворки), более подходящие к конкретным задачам.
                            Поэтому нет смысла хаять или хвалить jQuery, эта библиотека может быть полезна и сейчас для определенного круга задач. А может быть полезна и другая библиотека (фреймворк).
                            И я совершенно не понимаю, когда ругают за использование jQuery, но при этом ковыряют ее исходники и выдергивают оттуда код какого-то метода. Писали бы сами что ли, раз все так плохо.
                            И я, к примеру, часто использую jQuery в проектах, хотя понимаю, что есть оверхед по КПД использования методов библиотеки примерно 20-50%, ну и что. Сейчас интернет позволяет прогрузить +- 100-200 kB дополнительных данных в запросе, работает кеширование разных уровней, отложенные загрузки, минификации и другие инструменты оптимизации. Гораздо печальнее выглядят различные зависимости, вот где КПД использования зашкаливает — требуется зависимость ради одного метода — вот это мне кажется черезчур жестким и неоправданным. Поэтому для не хайлоад вполне пойдет и jQuery для облегчения писанины нативных методов, а там где хайлоад — там подходы и критерии к оптимизации совершенно отличаются от standart.
                            И все возражения против jQuery в настоящее время — просто дань моде, не более. Сейчас практически все специалисты используют инструменты по назначению — и, если будет необходимость в jQuery — ее будут использовать. Без оглядки на моду.
                            P.S. Не холивара ради, истины для.
                            • 0

                              Не заметил, чтобы автор хаял jQuery. Это, бесспорно, хорошая библиотека. Но нет ничего плохого в том, чтобы показать, как можно некоторые вещи делать без неё. Кто хочет, будет и дальше использовать jQuery, кто-то другой решит, что можно обойтись чистым JS, только и всего :)

                              • 0
                                Ранее использовали jQuery потому что:
                                — можно сделать короче и понятнее код,
                                — это было модно.

                                Есть и еще одна веская причина, не упомянутая Вами, а именно кроссбраузерность. Особенно это было актуально при необходимости поддержки древних IE, приводящей при использовании голого JavaScript к упомянутой автором классической схеме:
                                function getXmlHttp(){
                                  var xmlhttp;
                                  try {
                                    xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
                                  } catch (e) {
                                    try {
                                      xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
                                    } catch (E) {
                                      xmlhttp = false;
                                    }
                                  }
                                  if (!xmlhttp && typeof XMLHttpRequest !== 'undefined') {
                                    xmlhttp = new XMLHttpRequest();
                                  }
                                  return xmlhttp;
                                }
                                
                              • +2
                                Уважаемые друзья из мира Javascript!
                                Выучите уже наконец несложную разницу между библиотекой (или даже «библиотекой библиотек») и фреймворком.

                                Имя этой разнице — «инверсия управления»:
                                — Если вы используете библиотеку, то ваш код вызывает методы библиотеки по необходимости.
                                — Если вы используете фреймворк, то вы пишете код, который будет вызван фреймворком по некоему событию.

                                Всё очень просто и никак не зависит от языка и года на календаре :)
                                • 0

                                  Не описана работа с DOM. Переводил пару лет назад интересный материал на эту тему http://rachkov.pro/blog/22

                                  • 0
                                    Никогда не пользовался jQuery, но jQuery меня бесит) Почти на любой запрос в гугле как что-то сделать с DOM (и не только) на JS, показываются результаты как это сделать на jQuery)
                                    • +1
                                      Чтобы не показывал варианты jQuery, нужно писать запрос следующим образом: «Тут то, что я хочу найти -jquery». Знак "-" будет обозначать, что вам не нужны варианты jQuery

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.