Вариант миграции с JQuery на чистый Javascript

    Библиотека JQuery была создана в 2006 году для восполнения недостающей функциональности Javascript. С тех пор последний достаточно продвинулся в своем развитии, чтобы разработчики могли обходиться без JQuery, основная проблема которой — производительность.



    На Хабре было несколько статей с объективными замерами «тормознутости» JQuery на селекторных запросах.
    // jQuery 2.0
    var c = $("#comments .comment");
    4,649 ms
    // jQuery 2.0 
    var c = $(".comment");
    3,437 ms
    // native querySelectorAll
    var c = document.querySelectorAll("#comments .comment");
    1,362 ms
    // native querySelectorAll
    var c = document.querySelectorAll(".comment");
    1,168 ms
    // native getElementById / getElementsByClassName
    var n = document.getElementById("comments"); 
    var c = n.getElementsByClassName("comment");
    107 ms
    // native getElementsByClassName
    var c = document.getElementsByClassName("comment");
    75 ms
    (запуск в цикле по 10000 раз)

    В сети достаточно много хороших описаний аналогов JQuery функций на чистом Javascript-e — например, вот здесь.

    Но самая мощь JQuery — в лаконичности и красоте ее выражений. Даже психологически тяжело переписывать существующий код, меняя элегантные $() на многострочные конструкции.

    Попробуем, насколько возможно, оставить язык JQuery, [частично] заменив ее саму. Для этого нам нужно только или переопределить $() функцию, или заменить (что лучше) на свою — пусть это будет $jqr(). Она также будет возвращать объект, но уже «нативный» и не обремененный ненужными нам JQuery функциями.

    Пример кода:

    <html>
        <body>
            <p id="message"></p>
        </body>
    </html>

    JQuery код:

    $("#message").html("Hello world!");
    

    Меняется на:

    
    $jqr("#message").html("Hello world!");
    
    
    // JQuery Replacement
    function $jqr(sel) {
        return new JQR(sel);
    }
    
    class JQR {
        constructor(sel) {
            this.sel = sel;
            this.elements = document.querySelectorAll(sel);
        }
    
        html(str) {
            for (var i = 0; i < this.elements.length; i++) {
                this.elements[i].innerHTML = str;
            }
        }
    }
    

    В конструкторе класса желательно парсить sel, чтобы более эффективно применять querySelectorAll(), getElementsByClassName() и getElementById().

    Таким образом мы можем реализовывать в классе JQR только нужные нам функции, не выходя за рамки стандартного Javascript-а и не сильно трогая существующий код.

    Необязательно даже полностью избавляться от JQuery — частичная оптимизация уже даст результат.
    Share post

    Comments 78

      –1
      Наблюдается следующее: JS как ЯП очень популярный и востребованный имея море фреймворков и библиотек эволюционирует в кита с солянкой. Jquery появился потому что не было querySelectorAll. Ждем когда он в себя заберет текущие фреймворки?
        –2
        Фреймворк — это не библиотека, фреймворк — это не язык.
        Кроме JQuery распространенных таких библиотек нет.
        +1

        Что есть 'чистый javaScript'?


        jQuery — это не только методы для выборки

          0
          Речь идет не о переписке JQuery, а о миграции проекта. У меня в последнем, например, используется методов 5-10 из JQuery, все они могут быть так переписаны.

          Речь тут только о селекторных функциях. AJAX и другое — это отдельная история.
            +1
            Ну так это сложно назвать 'миграцией с jQuery', это 'миграция с 10 функций jQuery'.
            Не сочтите за занудство, но это всё таки абсолютно разные вещи.

            Плюс, было бы неплохо вводную в статью добавить, потому что всё прекрасно знают, что быстрее vanillajs ничего нет, но написать подобную обёртку для выборки элементов каждый может
              0
              Это миграция сайта с JQuery на Javascript ES6. Сколько JQuery функций используется на сайте не играет роли.
              Миграция в данном случае это метод и процесс, а не конкретная библиотека-заменитель.
                +1
                Ну так у вас ни метод ни процесс не описан.
                Нужно переписать функции jQuery — ну да, это логично.
                А что по поводу подводных камней, сложностей, каких то советов?

                Вашу статью можно уместить в одно предложение 'Напишите функции, подобные существующим в jQuery и используйте их, вместо jQuery' — ок

                С таким подходом, вы можете заменить jQuery в статье на что угодно (angular, knockout), раз вам не важно количество функции, тем более функция выборки везде есть, вот её и будем переписывать.

                  0
                  Вашу статью можно уместить в одно предложение 'Напишите функции, подобные существующим в jQuery и используйте их, вместо jQuery' — ок

                  Совершенно верно. Именно в этом смысл статьи.

                  Конечно с учетом того, что новые функции получаются на порядок производительней прежних и реализация их достаточно проста. Если вы можете добиться такого повышения эффективности и при переписывании angular или knockout и вам от них нужна только функция выборки, а не фреймворк — почему нет?
                    0
                    Совершенно верно. Именно в этом смысл статьи.

                    Был ли смысл целую статью писать?

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

                    До тех по пока Вы не пишете сложных функций. Не для всех функции есть аналоги в чистом js.

                    Если вы можете добиться такого повышения эффективности и при переписывании angular или knockout и вам от них нужна только функция выборки, а не фреймворк — почему нет?

                    Может быть потому что я выигрываю в поддержке, мне проще найти разработчиков, знакомых с angular, а не с моей (или Вашей) поделкой. Angular протестирован, есть много людей, которые его поддерживают, огромный зоопарк компонентов готовых и т.д.

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

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

            +2
            Частичная оптимизация не даст ничего, поскольку jquery с его селекторами обычно используется не ради селекторов как таковых а ради того что можно сделать с элементами выбранными посредством них. Для примера $('.foobar').addClass('bla').removeClass('tutu'), ну заменим мы селектор на натив, а дальше то что? Либо циклы, либо опять заворачивать в jquery, либо писать собственную реализацию, тем самым на выходе получив jquery #2
              –3
              Разве в статье не написано про реализацию нужных методов?
              addClass -> el.classList.add(className);
                0
                Разве я говорил об этом?
                  –2
                  Не знаю. Я не понимаю, о чем вы говорите.
                    0
                    Я не понимаю, о чем вы говорите.

                    Про циклы говорит:


                    $('.foobar').addClass('bla')

                    или


                    Array.from(document.getElementsByClassName("foobar")).forEach(el => {
                        el.classList.add('bla');
                    });
                      +2
                      В любом случае самым быстрым вариантом (увы и ах) будет простой советский for (var c = 0; c < els.length; c++) {… }
                        –2
                        В 99% случаев больше потеряете на выросшем от таких конструкций размере бандла.
                          0
                          Возможно и так, но если говорить о размере бандла то document.querySelectorAll и document.getElementById — претендуют на первое место в раздувании размера )
                +1
                уже все есть, зачем писать велосипеды?
                Array.from(document.querySelectorAll('a'))
                    .forEach(el => {
                       el.classList.add('test');
                       el.classList.remove('test');
                   })
                


                + меня бесит порядок аргументов в jquery в функциях ex.: each(index, element)
                  0
                  А вы попробуйте это прогнать по перформанса и посмотрите что будет быстрее
                    +1
                    Для Вас так критична производительность? Зачем Вы изначально jquery в таком случае брали?
                      0
                      Для меня то нет, а вот для автора похоже, что да. В ином случае это выкидывание jquery просто ради того чтобы его выкинуть.
                        0
                        Прошу прощения, вопрос автору адресован был, конечно же, просто веткой промахнулся как то
                          0
                          ок
                            0
                            Я от нее и сейчас не отказываюсь
                    • UFO just landed and posted this here
                        0
                        Шоб красиво было + по максимуму использовать весь доступный сахар.
                          0
                          Шоб красиво тогда уж
                          Object.prototype.find = function(s){return this.querySelectorAll(s);}
                          Object.prototype.bind = function(cssSelectors,events,callback,debug=false){
                            events = events.split(',');
                            elements= this.find(cssSelectors)||this;
                            elements.forEach(element=>{
                              events.forEach(event=>{
                                element.addEventListener(event,callback);
                                if (debug) console.dir(element+' listen '+event);
                              });
                            });
                          }
                          document.bind('a,p','click,mouseup',(e)=>{});
                          


                          Ну и в таком духе всё хождение по DOM'у можно задекорировать.
                          Object.prototype.on = function(f,c){return Array.from(this,(i)=>i.addEventListener(f,c));}
                          Object.prototype.find = function(s){return this.querySelectorAll(s);}
                          Object.prototype.first = function(s){if(!s) return this.firstElementChild;return this.querySelector(s);}
                          Object.prototype.next = function(){return this.nextElementSibling;};
                          Object.prototype.prev = function(){return this.previousElementSibling;};
                          Object.prototype.last = function(){return this.lastElementChild;};
                          Object.prototype.parents = function(cssSelectors){
                            if(!cssSelectors) return this.parentElement;
                              cssSelectors = cssSelectors.split(',');
                              const a=[];
                              for(let i=0;i<=cssSelectors.length;i++) {
                                var p=this.parentElement;
                                while(p){
                                  if (p.matches(cssSelectors[i])) {a.push(p);}
                                  p=p.parentElement;
                                }
                            }
                            return a;
                          };
                          
                            –1
                            Это пример того как нельзя делать…
                              0
                              В продакшне не стоит, накидать прототип интерфейса — почему нет?
                                –2
                                Прототип можно и на jquery накидать.
                                  –1
                                  Не стоит потому что можно на jquery? Ну ок.
                          0
                          developer.mozilla.org/ru/docs/Web/API/NodeList

                          Однако некоторые старые браузеры на данный момент все еще не поддерживают NodeList.forEach().
                          • UFO just landed and posted this here
                            • UFO just landed and posted this here
                                –1
                                Вот только Array.from можно заполифилить, а NodeList.forEach — не в любом браузере. Потому что Array — родной для JS объект, а NodeList — нативный.
                                • UFO just landed and posted this here
                                    –1

                                    Я не то слово сказал. Вот правильное: exotic object.


                                    Если у прототипа NodeList не объявлен внутренний метод [[DefineOwnProperty]] — то никаким полифилом вы метод forEach ему не добавите. Это возможно, к примеру, в старых версиях IE где объекты DOM были COM-объектами.

                                    • UFO just landed and posted this here
                            0
                            Порядку аргументов есть извинение: в jQuery .each появился раньше, чем .forEach в Array.prototype.
                          +6
                          Итого: мы выбрасываем jQuery, и пишем свой jQuery, только без тестов и кастрированный. Отлично. Ну и смысл?

                          И вообще, если страница такая нагруженная что для неё каждая миллисекунда выигрыша это важно — то что вообще там jQuery делает?
                            –6
                            Ну и смысл?
                            Смысл, наверное, в том, чтобы читать статью и пытаться понять, о чем она, а не выхватывать из нее знакомые слова, придумывать на лету свои фантазии, вытаскивать свои фобии и бороться с ними в комментариях.
                              +4
                              Ну-ка перечислите фантазии в моём комментарии. Или фобии (вот это вообще супер).

                              Вы предлагаете писать «свой jQuery» — это не фантазия, так и есть, вы предлагаете писать свою обёртку совместимую с jQuery.

                              Без тестов — вы правда написали тесты для своей поделки? «прогнал руками в консоли» — не считается

                              «Кастрированный» — ну очевидно же, тут вся суть в том что это будет неполноценный jQuery.

                              Ну и так далее. Воспринимайте критику проще.
                              0
                              Присоединяюсь
                              0
                              Здравствуйте. Благодарю за статью — интересный подход. Интересно, можно ли подобным образом реализовать миграцию jquery ajax?
                                –1
                                А почему нет? jQuery ajax же написан на JS. Можно спокойно под тот же fetch переписать. Любую функцию можно переписать вручную.
                                  +1
                                  Нет, ну что вы, это нереально.
                                    –1
                                    А чем стандартный fetch или его полифил не устраивает?
                                    Смысл приведенной в статье миграции — чтобы не переписывать весь код, по селекторам он обычно довольно объемный. А AJAX вызовов обычно немного, перевести их на fetch не должно составить труда.
                                    // jQuery
                                    $(selector).load(url, completeCallback)
                                    
                                    // Native
                                    fetch(url).then(data => data.text()).then(data => {
                                      document.querySelector(selector).innerHTML = data
                                    }).then(completeCallback)
                                      +2
                                      Оп, и у вас потерялись куки.
                                        0
                                        с куками
                                        fetch(url, {
                                          credentials: "same-origin"
                                        }).then(data => data.text()).then(data => {
                                          document.querySelector(selector).innerHTML = data
                                        }).then(completeCallback)
                                        • UFO just landed and posted this here
                                    0
                                    Не помешало бы тесты сделать общедоступными, а то тестирования выглядит мягко говоря «сферическим котом в вакууме». Может вы там для jQuery скормили страницу на пол мегабайта, а JS ваш пример с тремя нодами.
                                    В добавок почему сравнение идет с jQuery 2.0? Последняя версия 3.3.1 и между 2 и 3 версией огромная разница в скорости.

                                    И примеры никудышные, попробуйте выбрать чистым JS вот такое:
                                    $('#form[name*="regsitr"] .field-wrap:eq(4) :checkbox:checked')


                                    jQuery выбирают для удобства разработки. Если вам нужна скорость, вы можете комбинировать стандартные функции и методы jQuery.

                                    Но все эти «переходы» сразу прекращаются когда речь заходит о событиях…
                                    • UFO just landed and posted this here
                                        +1
                                        Чутка ошиблись:
                                        document.querySelectorAll('#form[name*="regsitr"] .field-wrap:nth-of-type(5) [type=checkbox]:checked')
                                        +4
                                        Справедливости ради: такие селекторы — вернейший признак говнокода.
                                        +2
                                        А теперь покажите код на чистом js для

                                        $('.some-node').wrap('<div class="wrapper"></div>')
                                                       .fadeOut(function(event) {
                                                           $(this).closest('.wrapper')
                                                                  .slideUp();
                                                       });
                                        
                                        • UFO just landed and posted this here
                                            0
                                            Во-первых, jQuery для анимации использует requestAnimationFrame, если эта функция доступна, то есть тот же механизм, что и транзишены. Во-вторых, не подскажите чем делать fadeOut на 10-м IE?
                                            • UFO just landed and posted this here
                                                +1
                                                Но как, если поддержка транзишенов появилась только в 11-м IE?
                                                • UFO just landed and posted this here
                                                    –2
                                                    Возможно. Впрочем, это не сильно изменяет ситуацию, так как на поддерживаемом мной ресурсе постоянных пользователей с IE8 больше тысячи человек.
                                                      0
                                                      Интересно, кто больше тормозит прогресс: Билл Гейтс со своим IE8, пользователи, которые на нем сидят, или разработчики, поддерживающие этих пользователей на их IE8
                                                        +1
                                                        Существует такое мнение, что IE11 будет существовать ещё очень-очень долго. Доля его стабилизируется на уровне пары процентов — вроде бы и немного, но не настолько, чтобы полностью ими пренебречь.
                                                        Причина — именно в его устаревших/маргинальных/нестандартных технологиях. У большинства они как заноза в заднице, но некоторым людям и компаниям они наоборот необходимы. И видели они ваш абстрактный прогресс в белых тапках, у них есть конкретные потребности.
                                                        В общем, ресурсы, рассчитанные на широкую аудиторию, вынуждены будут поддерживать IE ещё несколько лет.
                                                          0
                                                          Боюсь, что у разработчиков может и не быть выбора. Например, если основная аудитория сайта — пожилые жители маленького провинциального городка, то заказчик в договоре зафиксирует необходимость нормально отображаться на очень старых компьютерах с очень старыми браузерами.
                                                            –2
                                                            Если заказчик понимает, что за те же деньги, но без поддержки рудиментов он может получить продукт с на 20% большей функциональностью, то вопросов нет. Это его выбор.
                                                              +2
                                                              Есть репутационные издержки, которые проще упредить, чем потом бороться с ними.
                                                              Например, я продаю водяные краны. Ко мне на сайт заходит какой-то клерк из ЖЭКа со своего старенького пентиума и видит полностью поломаный сайт. Вы наверное думаете, что он сразу же поймёт, что проблема в его браузере? Да щас. Он решит, что сайт хлам и как пить дать контора раздолбайская. Больше того, он потом ещё своему начальнику скажет, что эта контора (как там она называется? забыл. да и черт с ней) гавно.
                                                              Зато на сайте чистенький ES6, ога.
                                                                0
                                                                Во-первых, он половину сайтов видит в таком виде, и если не совсем дурак, то поймет, в чем дело (верней вы ему явно об этом большими буквами вверху страницы напишите)
                                                                Во-вторых, для таких случаев удобно делать отдельный шаблон с минимумом функциональности на дубовом HTML без шика. Наверно все CMS это позволяют (несколько шаблонов). Это опять же может быть проще, чем делать один шаблон для всех ПК.
                                                                  0
                                                                  Вы напишете. Он скажет ну, что же понятно, что купить, здесь не выйдет. В результате купит на сайте из второй половины. Ведь затраты на новый компьютер или обновление программного обеспечения будут больше чем стоимость товара, который он у вас хотел купить.
                                                                0
                                                                В том то и дело, что на порядок большие деньги. Вы исходите только из затрат на сайт. А заказчик учитывает и затраты на другое программное обеспечение и затраты на оборудование и затраты на внедрение этого программного обеспечения и затраты на обучение персонала и возможно изменения бизнес-процессов. И бах таких рабочих мест может быть не 10 и даже не 100, а тысячи.
                                              0
                                              В конструкторе класса желательно парсить sel, чтобы более эффективно применять querySelectorAll(), getElementsByClassName() и getElementById()
                                              Это прекрасно. Сколько оверхеда даст парсинг селектора?
                                                –1
                                                Думаете, простая работа со строками (разбить по пробелам, определить тип: тэг-класс-id) занимает больше времени, чем проходы по DOM-у?
                                                  0
                                                  Получится что-то в таком духе? (код не тестировал, написано просто из головы)
                                                  function $ (selector) {
                                                  	const chunks = selector.trim().split(/\s+/);
                                                  	if (chunks.length === 1) {
                                                  		const firstChar = chunks[0].charAt(0);
                                                  		const word = chunks[0].slice(1);
                                                  		if (firstChar === '#') {
                                                  			return document.getElementById(word);
                                                  		}
                                                  		else if (firstChar === '.') {
                                                  			return document.getElementsByClassName(word);
                                                  		}
                                                  	}
                                                  	else {
                                                  		return document.querySelectorAll(selector);
                                                  	}
                                                  }

                                                  Думаю, что да, это будет медленнее.

                                                  А в реальности код будет ещё сложнее, потому что у меня не учтены по-нормальному атрибуты и ряд других нюансов.
                                                +1

                                                Всю статью можно было заменить этой ссылкой: You Might Not Need jQuery


                                                Только вот внезапно окажется, что не вся функциональность jQuery пишется в одну-две строчки:


                                                // jQuery
                                                $('.inner').wrap('<div class="wrapper"></div>');
                                                
                                                // Native
                                                document.querySelectorAll('.inner').forEach(el => {
                                                  const wrapper = document.createElement('div');
                                                  wrapper.className = 'wrapper';
                                                  el.parentNode.insertBefore(wrapper, el);
                                                  el.parentNode.removeChild(el);
                                                  wrapper.appendChild(el);
                                                });

                                                И если у нас реально много ручных DOM-манипуляций, то jQuery все еще остается хорошим выбором. Хотя лучше, конечно, взять какой-нибудь фреймворк из "большой тройки" :)

                                                  0

                                                  Поправка: el.parentNode.removeChild(el); можно не делать.

                                                  0
                                                  -
                                                  • UFO just landed and posted this here
                                                      +2

                                                      Наговнокодили, лишились кроссбраущерности, но выиграли пару наносекунд

                                                      Only users with full accounts can post comments. Log in, please.