Pull to refresh

bala.js — убийца jQuery в менее чем 400 символах кода *

Reading time6 min
Views43K
* Это шутка.

image
(картинка позаимствована где-то в интернете)

[ Репозиторий ]

Всем привет.

Уже давно прошли времена обязательной поддержки 6, 7, 8 Ослов и неизбежного использования jQuery, DOM API постепенно приводится к единому виду, но я всё так же часто встречаю на просторах интернета утверждения о том, что VanillaJS — это длинная колбаса.

Мол, зачем мне писать вот так:
document.querySelector('.selector');

Если я могу написать вот так:
$('.selector');

Я отчасти согласен с этим, потому что, периодически, необходимо выбирать элементы много раз в приложении. Приходится определять функции, делающую выборку, прямо в коде:

// selects one node matched given selector
function $(selector, ctx) {
	return (ctx || document).querySelector(selector);
}

// selects all nodes matched given selector
function $$(selector, ctx) {
	return [].slice.call((ctx || document).querySelectorAll(selector));
}

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

Но я совершенно несогласен с тем, что для обычной выборки нужно подклюать что-то большое (Zepto, jQuery).

«Движение» против использования jQuery и за использование нативных методов DOM существует уже несколько лет. Вспомним два самых известных сайта youmightnotneedjquery и vanilla-js. Оба сразу отталкивают новичка устаревшими альтернативами. vanilla-js показывает ужасные примеры использования AJAX и анимаций, второй грешит только беспощадным XMLHttpRequest. Оба сайта ни слова не говорят о Fetch API.

Хотя, если присмотреться, и с анимациями у второго не всё гладко. В качестве альтернативы они предлагают воспользоваться transition, хотя CSS Animations существуют давно и, самое главное, Web Animations JavaScript API уже имплементирован в Хроме и Файерфоксе и неплохо полифилится для других браузеров.

***

Для того, чтоб получить небольшую DOM библиотеку с минимальным необходимым набором методов, я когда-то сделал библиотеку, с шуточным названием balalaika.js. Напомню, балалайка — jQuery-подобная микробиблиотека, с очень небольшим набором методов: on, off, is, extend.

Но и она устарела. Метод is потерял свою актуальность, так как метод matches стал кроссбраузерным. extend самоуничтожился, так как в JavaScript пришел Object.assign, on и off просто-напросто не нужны, по причине, озвученной выше: фреймворки.

Я решил немного обновить эту библиотеку, выпилив все методы и оставив только функциональность, отвечающу за выборку элементов. Так как это изменение полностью ломает совместимость с балалайкой, было решено вынести её в отдельный проект с другим названием «bala» — обрубленное старое название (как и библиотека), — «пуля» по-испански.

Кроме всего прочего, целью bala.js является улучшение того, что сейчас иногда называют «плагинами к VanillaJS». Я очень люблю библиотеки без зависимостей, но почти все они предлагают инициализировать скрипт с передачей DOM Element.

myAwesomeLib(document.getElementByID('block'));

В таких случаях мне бы хотелось иметь больше возможностей: передать селектор, передать NodeList или, на худой конец, инстанс jQuery. Подключив к такому инструменту ~400 символов кода, инициализация скрипта будет более гибкой.

Что добавлено?


Крайне часто, при выборке, требуется только одна нода (например, для вызова appendChild). Но каждый раз запрашивать нулевой элемент выборки никому не нравится.
$('.selector')[0].appendChild(node);

Поэтому, была добавлена симпатичная альтернатива в виде статичного метода $.one, который работает в точности так же, как и основная функция, но возвращает нулевой элемент или null
// всего одним символом  больше, а выглядит на тысячу символов лучше
$.one('.selector').appendChild(node); 

$.one, кроме всего, служит двум целям: не создавать дополнительную переменную (в таких библиотеках их обычно две: $$ и $) и оставить возможность симпатичного импорта.

import $ from 'balajs';

var $ = require('balajs');


Что осталось от Балалайки?


Возможность передать в функцию всё, что угодно: селектор, узел, массив, NodeList, jQuery и любой другой array-like объект
$('.one, two')
$(document.querySelectorAll('.selector'));
$(document.body);
$(element.children);
$(jQuery('.selector'));
$([document.querySelector('.one'), document.querySelector('.two')])

Поиск элемента в другом элементе
var elements = $('.my-selector', element);

DOM ready
$(function() {
  alert('DOM is ready');
});

Не забывайте, что вместо использования DOM ready можно просто указать скрипты в конце тега body
    ...
    <script src="app.js"></script>
  </body>
</html>

Парсинг
var div = $('<div><span class="yeah"></span></div>');

Парсинг контекстных элементов
Для персинга элементов, требующих контекст, нужно передать имя тега-родителя (он создается динамически), например, для парсинга td нужно передать tr, для парсинга tr нужно передать tbody, для парсинга option нужно передать select.
var cells = $('<td>foo</td><td>bar</td>', 'tr');

Плагины
Расширять bala.js можнно как и любую другую jQuery-подобную библиотеку.
$.fn.toggle = function(boolean) {
  this.forEach(function(item) {
    item.hidden = boolean;
  });
};

$('.button').toggle(false); // hides all buttons


Как использовать?


Как глобальную переменную на странице
<script>
$=function(d,e,c,f,g){c=function(a,b){return new f(a,b)};f=function(a,b){e.push.apply(this,a?a.nodeType||a==window?[a]:""+a===a?/</.test(a)?((g=d.createElement(b||"q")).innerHTML=a,g.children):(b&&c(b)[0]||d).querySelectorAll(a):/f/.test(typeof a)?/c/.test(d.readyState)?a():d.addEventListener("DOMContentLoaded",a):a:e)};c.fn=f.prototype=e;c.one=function(a,b){return c(a,b)[0]||null};return c}(document,[]);
</script>

<script>
    $(function() {
        alert($('.my-selector').length);
    });
</script>

Как локальную переменную в IIFE
(function(win, $) {
    // your code starts here
    $(function() {
        alert($('.my-selector').length);
    });
  // your code ends here
})(window, function(d,e,c,f,g){c=function(a,b){return new f(a,b)};f=function(a,b){e.push.apply(this,a?a.nodeType||a==window?[a]:""+a===a?/</.test(a)?((g=d.createElement(b||"q")).innerHTML=a,g.children):(b&&c(b)[0]||d).querySelectorAll(a):/f/.test(typeof a)?/c/.test(d.readyState)?a():d.addEventListener("DOMContentLoaded",a):a:e)};c.fn=f.prototype=e;c.one=function(a,b){return c(a,b)[0]||null};return c}(document,[]));

Можно установить bala.js с помощью NPM
npm install --save balajs

Больше примеров


Перебор
True-way
for(let element of $('.button')) {
  console.log(element);
}

или old-school-way
$('.button').forEach(function(element) {
  console.log(element);
});

Добавление стилей
$('.my-selector').forEach(function(element) {
    element.style.color = 'red';
});

или, если нужно изменить стиль только для одного элемента:
$.one('.my-selector').style.color = 'red';

Делегирование событий
С методом closest не нужно использовать страшный while.
$('.my-selector').forEach(function(element) {
  element.addEventListener('click', function(evt) {
    if(this.contains(evt.target.closest('.delegated-selector'))) {
      alert('yep!');
    }
  });
});

или так
$.one('.my-selector').addEventListener('click', function(evt) {
  if(this.contains(evt.target.closest('.delegated-selector'))) {
    alert('yep!');
  }
});

Удаление элементов
$('.my-selector').forEach(function(element) {
    element.remove();
});

Удаление одного элемента
$.one('.my-selector').remove();

Вам может понадобиться полифил DOM4 для методов element.remove и element.closest.

(Эти примеры, конечно же, не являются заслугой bala.js. Это заслуга разработчиков браузеров и спецификаций.)

Вам всё еще нужен jQuery?

Если да, ниже еще два примера.

Анимации

С помощью Web Aimations API можно прописывать анимации прямо в JS коде (они будут многократно быстрее, чем таковые из jQuery, благодаря GPU ускорению).

// код позаимствован из гугловской статьи
$.one('.my-selector').animate([
  {transform: 'translate(' + snowLeft + 'px, -100%)'},
  {transform: 'translate(' + snowLeft + 'px, ' + window.innerHeight + 'px)'}
], {
  duration: 1500,
  iterations: 10,
  delay: 300
});


Само собой, это работает и без всяких библиотек (не счтиая того, что вам может понадобиться полифилл Web Animations API).

Ajax
А вот пример обращения к серверу. Fetch API элегантно заменяет устрашающий XMLHttpRequest.
fetch('user.json')
  .then(function(response) {
    return response.json();
   })
  .then(function(user) {
    console.log(user);
  })
  .catch(alert);

Вам может понадобиться полифил Fetch API.

Всё еще хотите подключить jQuery? Серьезно?

Да, для поддержки старых Internet Explorer и Webkit-based мобильных браузеров вам придется подключить полифилы. Но не спешите кидаться какашками и вспомните, что Internet Explorer, с недавних пор, ведет себя хорошо, имплементируя самые невероятные фичи раньше, чем это делают люди занимающиеся Blink и V8, обещая в скором времени отказаться от поддержки всех версий до 11; люди покупают новые айфоны с обновленным сафари…

А еще, предлагаю ознакомиться с этим сервисом: polyfill.io. Ребята полифилят фичи глядя на User-Agent посетителя страницы. Спасибо chico и RReverser за находку.

Через год-два в этих полифилах не будет нужды, но появятся новые, и ваш покорный слуга, возможно, снова напишет пост и предложит вам отказаться от jQuery и расскажет о библиотеке ba.js.
Tags:
Hubs:
Total votes 91: ↑58 and ↓33+25
Comments193

Articles