На Хабре было несколько статей с объективными замерами «тормознутости» JQuery на селекторных запросах.
|
4,649 ms |
|
3,437 ms |
|
1,362 ms |
|
1,168 ms |
|
107 ms |
|
75 ms |
В сети достаточно много хороших описаний аналогов 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 — частичная оптимизация уже даст результат.
Комментарии (73)
dopusteam
07.08.2018 07:52Что есть 'чистый javaScript'?
jQuery — это не только методы для выборки
altrus Автор
07.08.2018 07:56Речь идет не о переписке JQuery, а о миграции проекта. У меня в последнем, например, используется методов 5-10 из JQuery, все они могут быть так переписаны.
Речь тут только о селекторных функциях. AJAX и другое — это отдельная история.dopusteam
07.08.2018 09:24+1Ну так это сложно назвать 'миграцией с jQuery', это 'миграция с 10 функций jQuery'.
Не сочтите за занудство, но это всё таки абсолютно разные вещи.
Плюс, было бы неплохо вводную в статью добавить, потому что всё прекрасно знают, что быстрее vanillajs ничего нет, но написать подобную обёртку для выборки элементов каждый можетaltrus Автор
07.08.2018 09:35Это миграция сайта с JQuery на Javascript ES6. Сколько JQuery функций используется на сайте не играет роли.
Миграция в данном случае это метод и процесс, а не конкретная библиотека-заменитель.dopusteam
07.08.2018 09:45Ну так у вас ни метод ни процесс не описан.
Нужно переписать функции jQuery — ну да, это логично.
А что по поводу подводных камней, сложностей, каких то советов?
Вашу статью можно уместить в одно предложение 'Напишите функции, подобные существующим в jQuery и используйте их, вместо jQuery' — ок
С таким подходом, вы можете заменить jQuery в статье на что угодно (angular, knockout), раз вам не важно количество функции, тем более функция выборки везде есть, вот её и будем переписывать.
altrus Автор
07.08.2018 09:50Вашу статью можно уместить в одно предложение 'Напишите функции, подобные существующим в jQuery и используйте их, вместо jQuery' — ок
Совершенно верно. Именно в этом смысл статьи.
Конечно с учетом того, что новые функции получаются на порядок производительней прежних и реализация их достаточно проста. Если вы можете добиться такого повышения эффективности и при переписывании angular или knockout и вам от них нужна только функция выборки, а не фреймворк — почему нет?dopusteam
07.08.2018 09:56Совершенно верно. Именно в этом смысл статьи.
Был ли смысл целую статью писать?
Конечно с учетом того, что новые функции получаются на порядок производительней прежних и реализация их достаточно проста.
До тех по пока Вы не пишете сложных функций. Не для всех функции есть аналоги в чистом js.
Если вы можете добиться такого повышения эффективности и при переписывании angular или knockout и вам от них нужна только функция выборки, а не фреймворк — почему нет?
Может быть потому что я выигрываю в поддержке, мне проще найти разработчиков, знакомых с angular, а не с моей (или Вашей) поделкой. Angular протестирован, есть много людей, которые его поддерживают, огромный зоопарк компонентов готовых и т.д.
Даже если мне нужна небольшая страница, где никогда ничего развиваться не будет, я бы предпочёл использовать готовый продукт и сфокусироваться на бизнес логике, а не стотысячном переписывании существующего функционала
mayorovp
07.08.2018 10:13Конечно с учетом того, что новые функции получаются на порядок производительней прежних и реализация их достаточно проста.
Это возможно только при потере части функциональности. То есть все равно придется просмотреть весь старый код чтобы понять какими фичами можно будет пожертвовать.
token
07.08.2018 08:13-1Частичная оптимизация не даст ничего, поскольку jquery с его селекторами обычно используется не ради селекторов как таковых а ради того что можно сделать с элементами выбранными посредством них. Для примера $('.foobar').addClass('bla').removeClass('tutu'), ну заменим мы селектор на натив, а дальше то что? Либо циклы, либо опять заворачивать в jquery, либо писать собственную реализацию, тем самым на выходе получив jquery #2
altrus Автор
07.08.2018 09:07Разве в статье не написано про реализацию нужных методов?
addClass -> el.classList.add(className);token
07.08.2018 09:15Разве я говорил об этом?
altrus Автор
07.08.2018 09:27-1Не знаю. Я не понимаю, о чем вы говорите.
Riim
07.08.2018 14:58Я не понимаю, о чем вы говорите.
Про циклы говорит:
$('.foobar').addClass('bla')
или
Array.from(document.getElementsByClassName("foobar")).forEach(el => { el.classList.add('bla'); });
token
07.08.2018 14:59В любом случае самым быстрым вариантом (увы и ах) будет простой советский for (var c = 0; c < els.length; c++) {… }
akeinhell
07.08.2018 09:10уже все есть, зачем писать велосипеды?
Array.from(document.querySelectorAll('a')) .forEach(el => { el.classList.add('test'); el.classList.remove('test'); })
+ меня бесит порядок аргументов в jquery в функциях ex.: each(index, element)srhbdv
07.08.2018 09:28+1Зачем нужен Array.from?
document.querySelectorAll('a').forEach(e => { e.classList.add('test') e.classList.remove('test') })
akeinhell
07.08.2018 10:16developer.mozilla.org/ru/docs/Web/API/NodeList
Однако некоторые старые браузеры на данный момент все еще не поддерживают NodeList.forEach().
srhbdv
07.08.2018 10:18Некоторые старые браузеры никогда и не будут его уже поддерживать, как и все остальное из новых спецификаций. Данные ограничения, как и все остальные подобные, надо обходить единичной подгрузкой полифила.
srhbdv
07.08.2018 10:27Array.from точно так же не поддерживается старыми браузерами.
mayorovp
07.08.2018 10:37Вот только Array.from можно заполифилить, а NodeList.forEach — не в любом браузере. Потому что Array — родной для JS объект, а NodeList — нативный.
srhbdv
07.08.2018 10:47Что за бред вы пишите. NodeList.forEach точно так же без проблем полифилится. И Array — это точно такой же нативный встроенный объект, как и NodeList.
mayorovp
07.08.2018 10:52Я не то слово сказал. Вот правильное: exotic object.
Если у прототипа NodeList не объявлен внутренний метод [[DefineOwnProperty]] — то никаким полифилом вы метод forEach ему не добавите. Это возможно, к примеру, в старых версиях IE где объекты DOM были COM-объектами.
srhbdv
07.08.2018 10:55+1Если мы будем рассматривать те самые старые версий IE, то полифилить NodeList.forEach не будет никакой необходимости, потому как и querySelector там нет, и много чего еще.
И если честно, уже не смешно в 2018ом году, ссылаться на старые версии IE.
Bhudh
07.08.2018 23:32Порядку аргументов есть извинение: в jQuery
.each
появился раньше, чем.forEach
вArray.prototype
.
vlreshet
07.08.2018 09:24+3Итого: мы выбрасываем jQuery, и пишем свой jQuery, только без тестов и кастрированный. Отлично. Ну и смысл?
И вообще, если страница такая нагруженная что для неё каждая миллисекунда выигрыша это важно — то что вообще там jQuery делает?altrus Автор
07.08.2018 09:30-3Ну и смысл?
Смысл, наверное, в том, чтобы читать статью и пытаться понять, о чем она, а не выхватывать из нее знакомые слова, придумывать на лету свои фантазии, вытаскивать свои фобии и бороться с ними в комментариях.vlreshet
07.08.2018 09:34+2Ну-ка перечислите фантазии в моём комментарии. Или фобии (вот это вообще супер).
Вы предлагаете писать «свой jQuery» — это не фантазия, так и есть, вы предлагаете писать свою обёртку совместимую с jQuery.
Без тестов — вы правда написали тесты для своей поделки? «прогнал руками в консоли» — не считается
«Кастрированный» — ну очевидно же, тут вся суть в том что это будет неполноценный jQuery.
Ну и так далее. Воспринимайте критику проще.
denis_skripnik
07.08.2018 09:36Здравствуйте. Благодарю за статью — интересный подход. Интересно, можно ли подобным образом реализовать миграцию jquery ajax?
vlreshet
07.08.2018 09:38-1А почему нет? jQuery ajax же написан на JS. Можно спокойно под тот же fetch переписать. Любую функцию можно переписать вручную.
altrus Автор
07.08.2018 09:41-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)
vlreshet
07.08.2018 09:47Оп, и у вас потерялись куки.
altrus Автор
07.08.2018 09:54с куками
fetch(url, { credentials: "same-origin" }).then(data => data.text()).then(data => { document.querySelector(selector).innerHTML = data }).then(completeCallback)
srhbdv
07.08.2018 09:56-1Откройте для себя уже element.textContent, если вы так гонитесь за производительностью.
stepmex
07.08.2018 09:57Не помешало бы тесты сделать общедоступными, а то тестирования выглядит мягко говоря «сферическим котом в вакууме». Может вы там для jQuery скормили страницу на пол мегабайта, а JS ваш пример с тремя нодами.
В добавок почему сравнение идет с jQuery 2.0? Последняя версия 3.3.1 и между 2 и 3 версией огромная разница в скорости.
И примеры никудышные, попробуйте выбрать чистым JS вот такое:
$('#form[name*="regsitr"] .field-wrap:eq(4) :checkbox:checked')
jQuery выбирают для удобства разработки. Если вам нужна скорость, вы можете комбинировать стандартные функции и методы jQuery.
Но все эти «переходы» сразу прекращаются когда речь заходит о событиях…
Hivemaster
07.08.2018 10:12+2А теперь покажите код на чистом js для
$('.some-node').wrap('<div class="wrapper"></div>') .fadeOut(function(event) { $(this).closest('.wrapper') .slideUp(); });
srhbdv
07.08.2018 10:15Больше не делайте fadeOut на CPU, пожалуйста. Для этого давно есть css.
Hivemaster
07.08.2018 10:37Во-первых, jQuery для анимации использует requestAnimationFrame, если эта функция доступна, то есть тот же механизм, что и транзишены. Во-вторых, не подскажите чем делать fadeOut на 10-м IE?
srhbdv
07.08.2018 10:38Нет, requestAnimationFrame — это не тот же механизм, что транзишены.
Во-вторых, не подскажите чем делать fadeOut на 10-м IE?
Конечно подскажу. На css.Hivemaster
07.08.2018 10:42Но как, если поддержка транзишенов появилась только в 11-м IE?
srhbdv
07.08.2018 10:44Нет, вы ошибаетесь. И css-transition и css-animation поддерживаются IE начиная с версии 10.
Hivemaster
07.08.2018 10:51-1Возможно. Впрочем, это не сильно изменяет ситуацию, так как на поддерживаемом мной ресурсе постоянных пользователей с IE8 больше тысячи человек.
altrus Автор
07.08.2018 11:28Интересно, кто больше тормозит прогресс: Билл Гейтс со своим IE8, пользователи, которые на нем сидят, или разработчики, поддерживающие этих пользователей на их IE8
dom1n1k
07.08.2018 11:54+1Существует такое мнение, что IE11 будет существовать ещё очень-очень долго. Доля его стабилизируется на уровне пары процентов — вроде бы и немного, но не настолько, чтобы полностью ими пренебречь.
Причина — именно в его устаревших/маргинальных/нестандартных технологиях. У большинства они как заноза в заднице, но некоторым людям и компаниям они наоборот необходимы. И видели они ваш абстрактный прогресс в белых тапках, у них есть конкретные потребности.
В общем, ресурсы, рассчитанные на широкую аудиторию, вынуждены будут поддерживать IE ещё несколько лет.
TheDeadOne
07.08.2018 13:12Боюсь, что у разработчиков может и не быть выбора. Например, если основная аудитория сайта — пожилые жители маленького провинциального городка, то заказчик в договоре зафиксирует необходимость нормально отображаться на очень старых компьютерах с очень старыми браузерами.
altrus Автор
07.08.2018 13:26-1Если заказчик понимает, что за те же деньги, но без поддержки рудиментов он может получить продукт с на 20% большей функциональностью, то вопросов нет. Это его выбор.
dom1n1k
07.08.2018 14:25+1Есть репутационные издержки, которые проще упредить, чем потом бороться с ними.
Например, я продаю водяные краны. Ко мне на сайт заходит какой-то клерк из ЖЭКа со своего старенького пентиума и видит полностью поломаный сайт. Вы наверное думаете, что он сразу же поймёт, что проблема в его браузере? Да щас. Он решит, что сайт хлам и как пить дать контора раздолбайская. Больше того, он потом ещё своему начальнику скажет, что эта контора (как там она называется? забыл. да и черт с ней) гавно.
Зато на сайте чистенький ES6, ога.altrus Автор
07.08.2018 15:08Во-первых, он половину сайтов видит в таком виде, и если не совсем дурак, то поймет, в чем дело (верней вы ему явно об этом большими буквами вверху страницы напишите)
Во-вторых, для таких случаев удобно делать отдельный шаблон с минимумом функциональности на дубовом HTML без шика. Наверно все CMS это позволяют (несколько шаблонов). Это опять же может быть проще, чем делать один шаблон для всех ПК.krundetz
08.08.2018 16:08Вы напишете. Он скажет ну, что же понятно, что купить, здесь не выйдет. В результате купит на сайте из второй половины. Ведь затраты на новый компьютер или обновление программного обеспечения будут больше чем стоимость товара, который он у вас хотел купить.
krundetz
08.08.2018 15:53В том то и дело, что на порядок большие деньги. Вы исходите только из затрат на сайт. А заказчик учитывает и затраты на другое программное обеспечение и затраты на оборудование и затраты на внедрение этого программного обеспечения и затраты на обучение персонала и возможно изменения бизнес-процессов. И бах таких рабочих мест может быть не 10 и даже не 100, а тысячи.
dom1n1k
07.08.2018 10:14+1В конструкторе класса желательно парсить sel, чтобы более эффективно применять querySelectorAll(), getElementsByClassName() и getElementById()
Это прекрасно. Сколько оверхеда даст парсинг селектора?altrus Автор
07.08.2018 11:12-1Думаете, простая работа со строками (разбить по пробелам, определить тип: тэг-класс-id) занимает больше времени, чем проходы по DOM-у?
dom1n1k
07.08.2018 11:38Получится что-то в таком духе? (код не тестировал, написано просто из головы)
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); } }
Думаю, что да, это будет медленнее.
А в реальности код будет ещё сложнее, потому что у меня не учтены по-нормальному атрибуты и ряд других нюансов.
gnaeus
07.08.2018 10:18Всю статью можно было заменить этой ссылкой: 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 все еще остается хорошим выбором. Хотя лучше, конечно, взять какой-нибудь фреймворк из "большой тройки" :)
LazyProger
Наблюдается следующее: JS как ЯП очень популярный и востребованный имея море фреймворков и библиотек эволюционирует в кита с солянкой. Jquery появился потому что не было querySelectorAll. Ждем когда он в себя заберет текущие фреймворки?
altrus Автор
Фреймворк — это не библиотека, фреймворк — это не язык.
Кроме JQuery распространенных таких библиотек нет.