15 января в 13:50

О создании улучшенной JavaScript-библиотеки для работы с DOM из песочницы

В настоящее время jQuery является де-факто библиотекой для работы с DOM. Она может использоваться вместе с популярными MV* фрэймворками (такими как Backbone), имеет множество плагинов и очень большое сообщество. С другой стороны JavaScript становится все популярнее и многие разработчики начинают интересоваться как работают стандартные API и когда можно просто их использовать, не добавляя дополнительную библиотеку.

В поздние дни работы с jQuery я начал замечать разные проблемы с этой библиотекой. Большинство из них основопологающие, поэтому не могут быть исправлены без потери обратной совместимости, что, конечно, важно. Я, как и многие другие, продолжал использовать библиотеку какое-то время, встречаясь с надоедливыми причудами каждый день.

Затем Daniel Buchner создал SelectorListener, и родилась идея live расширений. Я начал подумывать о создании набора функций, который позволит создавать ненавязчивые и независимые компоненты DOM, используя лучший подход. Задача была сделать обзор существующих решений и создать более понятную, тестируемую, маленькую, но в то же время самодостаточную библиотеку.

Добавление полезных функций в библиотеку


Идея live расширений способствовала разработке проекта better-dom, хотя кроме него имеются другие интересные особенности, которые делают библиотеку уникальной. Давайте сделаем их беглый обзор:
  • live расширения
  • нативные анимации
  • встроенный шаблонизатор
  • поддержка интернационализации

Live расширения

В jQuery существует понятие live событий. За кулисами они используют event delegation чтобы обрабатывать существующие и будущие элементы. Однако во многих случаях требуется большая гибкость. Например, если виджет должен при инициализации добавить дополнительные элементы в дерево документа, которые должны взаимодействовать или замещать существующие, live события не работают. Чтобы решить проблему я представляю live расширения.

Цель — объявить расширение однажды, и после этого оно должно работать для будущего контента независимо от сложности виджета. Это важная особенность, поскольку позволяет создавать веб-страницы декларативно, поэтому хорошо подходит для AJAX приложений.

Рассмотрим простой пример. Допустим, наша задача реализовать полностью кастомизируемую всплывающую подсказку. Псевдоселектор :hover не подходит, потому что позиция тултипа меняется в зависимости от курсора мыши. Event delegation так же не подходит — слишком затратно слушать mouseover и mouseleave для всех элементов на странице. Здесь на сцену выходят live расширения.
DOM.extend("[title]", {
  constructor: function() {
    var tooltip = DOM.create("span.custom-title");

    // устанавливаем textContent всплывающей подсказки в значение 
    // title исходного элемента и скрываем ее изначально
    tooltip.set("textContent", this.get("title")).hide();

    this
      // удаляем нативный tooltip
      .set("title", null)
      // сохраняем ссылку для более быстрого доступа
      .data("tooltip", tooltip)
      // регистрируем обработчики событий
      .on("mouseenter", this.onMouseEnter, ["clientX", "clientY"])
      .on("mouseleave", this.onMouseLeave)
      // вставляем всплывающую подсказку в DOM
      .append(tooltip);
  },
  onMouseEnter: function(x, y) {
    this.data("tooltip").style({left: x, top: y}).show();
  },
  onMouseLeave: function() {
    this.data("tooltip").hide();
  }
});

Наш тултип теперь можно стилизировать с помощью селектора .custom-title в CSS:
.custom-title {
  position: fixed;
  border: 1px solid #faebcc;
  background: #faf8f0;
}

Однако самое интересное начинается, когда новые элементы с атрибутом title добавляются на страницу. Они подхватятся расширением без вызова какой-либо инициализирующей функции.

Live расширения самодостаточны, поэтому не нуждаются в дергании определенной функции, чтобы работать с будущим контентом. А значит могут комбинироваться с любой существующей библиотекой для DOM и упрощают логику приложения, разделяя UI код на множество маленьких независимых частей.

В заключение несколько слов о Web components. Один и разделов спецификации, под названием «Декораторы», предназначен для решения схожей проблемы. В настоящее время он использует разметку и специальный синтаксис для навешивания слушателей на дочерние элементы. Но это пока очень ранний черновик:
Декораторы, в отличие от других разделов Web Components не имеют пока спецификации.

Нативные анимации

Благодаря Apple в CSS сейчас есть хорошая поддержка анимаций. В прошлом анимации реализовывались на JavaScript с помощью setInterval и setTimeout. Это была классная фишка но теперь… что-то вроде плохой практики. Нативные анимации всегда будут более плавными: они обычно быстрее, требуют меньше энергии и просто не показываются в браузерах, которые их не поддерживают.

В better-dom нет метода animate: только show, hide и toggle. Чтобы захватить состояние скрытого элемента в CSS библиотека использует стандартизированный атрибут aria-hidden.

Для иллюстрации подхода давайте добавим простую анимацию тултипу, который мы написали ранее:
.custom-title {
  position: fixed;
  border: 1px solid #faebcc;
  background: #faf8f0;
  /* анимация */
  opacity: 1;
  -webkit-transition: opacity 0.5s;
  transition: opacity 0.5s;
}

.custom-title[aria-hidden=true] {
  opacity: 0;
}

Внутри show и hide атрибут aria-hidden меняет свое значение на false или true. Этого достаточно чтобы показывать анимации средствами CSS.

Больше примеров анимаций с помощью better-dom.

Встроенный шаблонизатор

HTML-строки громоздкие. Когда я начал искать замену, то нашел отличный проект Emmet. В настоящее время он достаточно популярный в качестве плагина для текстовых редакторов и имеет чистый и компактный синтаксис. Сравните:
body.append("<ul><li class='list-item'></li><li class='list-item'></li><li class='list-item'></li></ul>");

что эквивалентно
body.append("ul>li.list-item*3");

В better-dom методы, которые принимают HTML-строки в качестве аргументов, так же поддерживают emmet-аббревиатуры. Парсер аббревиатур быстрый, поэтому можно не думать о потерях в производительности. Так же имеется функция для прекомпиляции шаблонов, которая может быть использована по мере необходимости.

Поддержка интернационализации

Разработка UI-виджета часто требует локализацию, что не всегда простая задача. Многие решали эту проблему по-своему. С better-dom я надеюсь, что смена языка будет не сложнее изменения состояния CSS селектора.

С идеологической точки зрения переключение языка — это наподобие изменения «представления» контента. В CSS2 есть несколько псевдоселекторов, которые помогают описать такую модель: :lang и :before. Взгляните на код ниже:
[data-i18n="hello"]:before {
  content: "Hello Maksim!";
}

[data-i18n="hello"]:lang(ru):before {
  content: "Привет Максим!";
}

Хитрость в том, что свойство content меняется в соответствии с текущим языком, который определяется значением атрибута lang для элемента html. С помощью атрибута data-i18n мы можем использовать более общую запись:
[data-i18n]:before {
  content: attr(data-i18n);
}

[data-i18n="Hello Maksim!"]:lang(ru):before {
  content: "Привет Максим!";
}

Разумеется, такой CSS код не выглядит привлекательным, поэтому в better-dom два хелпера: i18n и DOM.importStrings. Первый используется для обновления атрибута data-i18n с соответствующим значением, а второй локализует строки для определенного языка.
label.i18n("Hello Maksim!");
// label отображает "Hello Maksim!"
DOM.importStrings("ru",  "Hello Maksim!", "Привет Максим!");
// теперь если язык страницы "ru", то label будет показывать "Привет Максим!"
label.set("lang", "ru");
// теперь label показывает "Привет Максим!" независимо от языка страницы

Параметризованные строки так же поддерживаются: достаточно добавить ${param} переменные в ключевую строку:
label.i18n("Hello ${user}!", {user: "Maksim"});
// label показывает "Hello Maksim!"

Улучшение нативных API


Обычно мы хотим придерживаться стандартов. Но иногда стандарты не совсем дружелюбные. DOM очень запутанный и, чтобы сделать его приятным, нужно обернуть его в удобный API. Несмотря на многочисленные улучшения, которые сделаны разными библиотеками, кое-какие вещи можно сделать лучше:
  • getter и setter
  • улучшенная обработка событий
  • поддержка функциональных методов

Getter и setter

Нативный DOM имеет понятия атрибутов и свойств у элемента, которые могут вести себя по-разному. Предположим на странице имеется разметка:
<a href="/chemerisuk/better-dom" id="foo" data-test="test">better-dom</a>

Чтобы объяснить недружелюбие нативного DOM, давайте поработаем с ним немного:
var link = document.getElementById("foo");

link.href; // => "https://github.com/chemerisuk/better-dom"
link.getAttribute("href"); // => "/chemerisuk/better-dom"
link["data-test"]; // => undefined
link.getAttribute("data-test"); // => "test"

link.href = "abc";
link.href; // => "https://github.com/abc"
link.getAttribute("href"); // => "abc"

Итак, значение атрибута равно соответствующей строке в HTML, в то время как свойство элемента с таким же именем может иметь специальное поведение, например, генерация полного URL в примере выше. Эта разница может иногда запутывать.

На практике сложно представить, когда такое разделение может быть полезным. Более того, разработчик должен все время следить за тем, с каким значением он работает, что добавляет ненужную сложность.

В better-dom дела обстоят проще: каждый элемент имеет только умный getter и setter.
var link = DOM.find("#foo");

link.get("href"); // => "https://github.com/chemerisuk/better-dom"
link.set("href", "abc");
link.get("href"); // => "https://github.com/abc"
link.get("data-attr"); // => "test"

На первом шаге методы делают поиск свойства элемента и, если оно определено, используют его для операций. В противном случае работают с соответствующим атрибутом. Для буленовских атрибутов (checked, selected и т.д.) можно просто использовать true или false. Изменение этих свойств у элемента обновляет соответствующий атрибут (нативное поведение).

Улучшенная обработка событий

Обработка событий — это значимая часть кодирования для DOM. Одна фундаментальная проблема, которую я обнаружил, состоит в том, что наличие event object в слушателях элемента заставляет разработчиков, которые любят тестируемый код, мОчить первый агрумент или создавать дополнительную функцию, которая принимает используемые в этом обработчике свойства события.
var button = document.getElementById("foo");

button.addEventListener("click", function(e) {
  handleButtonClick(e.button);
}, false);

Это на самом деле надоедает и добавляет вызов дополнительной функции. Что если выделить меняющуюся часть в качестве аргумента: это позволит избавиться от замыкания:
var button = DOM.find("#foo");

button.on("click", handleButtonClick, ["button"]);

По умолчанию обработчик событий принимает массив ["target", "defaultPrevented"], поэтому нет необходимости добавлять последний аргумент, чтобы прочитать эти свойства:
button.on("click", function(target, canceled) {
  // обрабатываем клик
});

Позднее связывание так же поддерживается (рекомендую прочитать статью от Peter Michaux по теме). Это более гибкая альтернатива обычным обработчикам событий, которая, кстати, присутствует в стандарте. Может быть полезна в случаях, когда нужно делать частые вызовы методов on и off.
button._handleButtonClick = function() { alert("click!"); };

button.on("click", "_handleButtonClick");
button.fire("click"); // показывается сообщение "clicked"
button._handleButtonClick = null;
button.fire("click"); // ничего не показывается

В завершении стоит упомянуть что в better-dom нету методов наподобие и click(), focus(), submit() т.п., которые присутствуют в стандарте и имеют различное поведение в браузерах. Единственные способ их вызвать — это использовать метод fire, который выполняет поведение по умолчанию, когда ни один из обработчиков не вернул false:
link.fire("click"); // кликает по ссылке
link.on("click", function() { return false; });
link.fire("click"); // вызывает обработчик выше но не кликает по ссылке

Поддержка функциональных методов

ES5 стандартизировал несколько полезных методов для массивов, такие как map, filter, some и т.д. Они позволяют проводить операции над коллекциями в стандартизированном виде. В результате сегодня имеются проекты наподобие Underscore или Lo-Dash, которые позволяют пользоваться этими методами в старых браузерах.

Каждый элемент (или коллекция) в better-dom имеет методы ниже из коробки:
  • each (отличается от forEach тем, что возвращает this вместо undefined)
  • some
  • every
  • map
  • filter
  • reduce[Right]

var urls, activeLi, linkText; 

urls = menu.findAll("a").map(function(el) {
  return el.get("href");
});
activeLi = menu.children().filter(function(el) {
  return el.hasClass("active");
});
linkText = menu.children().reduce(function(memo, el) {
  return memo || el.hasClass("active") && el.find("a").get()
}, false);

Решение некоторых проблем jQuery


Большинство проблем ниже не могут быть исправлены в jQuery без потери обратной совместимости. Еще одна причина, по которой было решено создать новую библиотеку.
  • «магическая» функция $
  • значение оператора квадратных скобок
  • проблемы с return false
  • find и findAll

«Магическая» функция $

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

За кулисами $ — это довольно сложная функция. Частое ее выполнение, особенно внутри таких событий как mousemove или scroll, может быть причиной плохой отзывчивости UI.

Несмотря на многочисленные статьи, которые продвигают кэширование объектов jQuery разработчики продолжают вставлять $. Это потому, что синтаксис библиотеки способствует такому стилю кодирования.

Другая проблема с этой функцией состоит в том, что она является ответственной за две совершенно разные задачи. Люди уже привыкли к такому синтаксису, но это нехорошая практика дизайна функции в общем случае:
$("a"); // => поиск всех элементов, которые соответствуют селектору “a”
$("<a>"); // => создает элемент <a> с jQuery врапером

В better-dom зоны ответственности $-функции покрывают несколько методов: find[All] и DOM.create. Методы find[All] используются для поиска элементов по CSS-селектору. DOM.create создает новые элементы в памяти. Имена функций ясно говорят что эти функции делают.

Значение оператора квадратных скобок

Еще одна причина проблемы с слишком частыми вызовами доллар-функции — это оператор квадратных скобок. Когда создается новый jQuery-объект все связанные элементы сохраняются в numeric-свойствах. Важно заметить, что значение такого свойства содержит экземпляр нативного элемента (не jQuery-врапера):
var links = $("a");

links[0].on("click", function() { ... }); // ошибка!
$(links[0]).on("click", function() { ... }); // теперь все хорошо

Из-за такой особенности каждый функциональный метод в jQuery или другой библиотеки (как Underscore) требует, чтобы текущий элемент оборачивался в $() внутри итерационной функции. Поэтому разработчик обязан всегда помнить с каким объектом он работает: нативным или врапером, несмотря на факт, что используется библиотека для работы с DOM.

В better-dom оператор квадратных скобок возвращает объект библиотеки, поэтому можно забыть о нативных элементах. Единственный легальный способ получить к ним доступ — это использовать специальный метод legacy.
var foo = DOM.find("#foo");

foo.legacy(function(node) {
  // используя библиотеку Hammer слушаем жест swipe
  Hammer(node).on("swipe", function(e) {
    // обрабатываем жест swipe
  }); 
});

Но в реальности он нужен в очень редких случаях, например, когда нужна совместимость с нативной функцией или другой DOM библиотекой (как Hammer из примера выше).

Проблемы с return false

Одна вещь, которая действительно взорвала мне мозг это странная обработка return false в слушателях событий. В соответствии с стандартами W3C это значение должно в большинстве случаев отменять поведение по умолчанию. В jQuery return false дополнительно останавливает event delegation!

Здесь сразу несколько проблем:
  1. вызов stopPropagation() сам по себе может создавать проблемы с совместимостью, т.к. он ломает возможность других слушателей делать их работу по возникновению такого события
  2. большинство разработчиков (даже опытных) не ожидают такого поведения

Непонятно почему сообщество jQuery решило пойти против стандартов. И better-dom не собирается повторять эту ошибку: return false внутри обработчика событий вызывает только preventDefault(), как и ожидается.

find и findAll

Поиск элементов — это одна из самых дорогих операций в браузере. Две нативные функции могут использоваться чтобы реализовать его: querySelector и querySelectorAll. Разница между ними в том, что первая останавливает поиск после первого совпадения.

Эта особенность позволяет значительно уменьшить количество итераций в подходящих случаях. В моих тестах выигрыш в скорости может быть до 20 раз. Так же можно ожидать что разрыв увеличивается в зависимости от размера дерева документа.

jQuery имеет find метод, который использует querySelectorAll в общем случае. На сегодняшний день здесь нет метода, который бы использовал querySelector, чтобы найти только первый подходящий элемент.

В better-dom есть два разных метода: find и findAll. Они позволяют использовать querySelector-оптимизацию выше. Чтобы оценить потенциальный выигрыш, я сделал выборку по количеству вхождений в исходном коде последнего коммерческого проекта:
  • find — 103 совпадений в 11 файлах
  • findAll — 14 совпадений в 4 файлах

Определенно, что метод find горазде более популярный. Это значит, что querySelector-оптимизация имеет место в большинстве случаев, поэтому может дать ощутимый выигрыш в производительности кода на клиенте.

Заключение


Разработка с использованием live расширений действительно упрощает жизнь на front-end. Разделение UI на множество маленьких частей помогает создавать более независимые (=надежные) решения. Но, как видно выше, better-dom не только о них (хотя это была изначальная главная цель).

Во время разработки я понял одну важную вещь: если текущие стандарты не совсем устраивают или есть идеи как можно сделать лучше — просто реализуй и докажи что они работают. И это очень весело!

Больше информации о библиотеке better-dom всегда можно найти на GitHub.
+14
8644
112

Комментарии (15)

+3
creage, #
Идея live расширений понравилось, а остальное — вода. Вместо «исправлений» в отдельной библиотеке, лучше бы пулили риквесты в jQuery.
+3
chemerisuk, #
С пул реквестами не все так просто, потому что имеется уже много написанного кода.
Большинство проблем ниже не могут быть исправлены в jQuery без потери обратной совместимости.

Есть еще проблема конфликта имен. Например, в будущем в нативном DOM появятся find и findAll, т.е. это уже стандартизированные имена методов. В jQuery find по смыслу эквивалентен findAll. Стоит ли добавлять findFirst и тем самым запутывать людей еще больше? Много вопросов.
0
GruZZ, #
О, спасибо, только вчера на SmashingMagazine прочитал, а тут и на Хабре статья подоспела.
0
spmbt, #
Тут ещё строили свою DOM-библиотеку: habrahabr.ru/post/204574/
А вообще,
«каждый уважающий себя javascript-программист должен написать:
1. свою реализацию классов
2. свой шаблонизатор
3. свой jQuery
4. свой Angular» © @ creage :)

(У меня уже есть половина реализации 3-го пункта, но выкладывать на гитхаб рано. На мой взгляд, заменитель должен быть крайне компактным, модульным и не обязательно на что-либо похожим, за исключением разве что нативных методов.)
+2
chemerisuk, #
Список, конечно, хороший, но начинать лучше с изучения существующих стандартов. Пункт 1, например, хорошо сделать для понимания как работает наследование в JavaScript. Но в ES6 появится стандартизированный синтаксис для объявления классов, поэтому через какое-то время «своя реализация классов» будет никому не нужной. Поэтому лучше инвестировать свое время на что-нибудь другое.

Изобретание велосипедов хорошо там, где пока еще нет стандартных решений либо они неудобные.
0
tenbits, #
:) У меня наоборот — всё кроме 3его пункта ... А вот про гитхаб вы не правы — выкладывать никогда не рано!
PS: Не пропустите ещё одно хорошее начинание по `jquery` теме — Kimbo
0
Gromo, #
А какие отличия данной реализации LIVE от jQuery.on? Тот же результат всплывающей подсказки можно сделать jQuery(document).on('mouseenter', '[title]', func), добавив для лучшей производительности в селектор только определённые элементы и/или классы.

P.S. По поводу SelectorListener: расширение прототипов нативных элементов браузеров — плохая практика. С этим столкнулись разработчики PrototypeJS.

0
chemerisuk, #
добавив для лучшей производительности в селектор только определённые элементы и/или классы

Вот здесь вы ошибаетесь. Live events работают по принципу фильтрации событий определенного типа на рутовом элементе. Количество происходящих событий с селектором и без него одинаковое.

Live extensions используют иной механизм навешивания событий. Если грубо, то можно воспринимать их как дешевый DOMNodeInserted. В отличие от live events селектор здесь влияет на количество происходящих событий. Поэтому они работают в тех случаях, когда live events использовать неэффективно.

По поводу SelectorListener: расширение прототипов нативных элементов браузеров — плохая практика.

better-dom не расширяет нативные прототипы (за исключением фиксов для старых IE).
0
Jabher, #
может, стоит посмотреть в сторону polymer?
0
chemerisuk, #
К минусам polymer я бы отнес то, что он работает только с кастомными элементами и слабую поддержку браузеров. Поэтому этот фреймворк сложновато использовать в реальных проектах.

Live extensions работают с любыми элементами и поддерживают IE8+ и я уже сейчас многие проекты на его основе использую по принципу подключил и забыл (не надо ничего инициализировать).
0
Jabher, #
тогда в сторону x-tags. там ie9+ за счет того, что нет shadow DOM. Работает оно по крайней мере честнее чем трюки с keyframes. Особенно с учетом того, что mutation events и MutationObserver нацелены на то, чтобы отлавливать события изменения дом-дерева. А @keyframes — нет.
0
chemerisuk, #
Я бы советовал вам получше посмотреть что такое mutation events и MutationObserver. Ни то, ни другое не позволяет эффективно отлавливать элементы по CSS селектору. Эти специификации не предназначены для этого.

Никто не любит хаков, и live extensions не занимаются продвижением уловки с @keyframes. Задача в том, чтобы начать использовать новую идею в реальных проектах, которая позволяет решать нетривиальные в прошлом задачи, и, соответвтвенно, мыслить другими категориями.
0
Jabher, #
ну, вы настойчиво хотите мыслить категориями прежними, даже с новыми технологиями. Не думаю, что кому-то нужно регистрировать события на '.users .userPreview .dropdown:nth-child(5)', например.

Не говоря о том, что сама архитектура достаточно некомфортна для использования.
Во-первых, не стоит завязывать части системы друг на друга. Даже jQuery передает в this нативные элементы — как из-за быстродействия, так и из-за совместимости с библиотеками. Обернуть в $() не составляет проблем, а ресурсы и читаемость значительно выше.
Во-вторых, если вы будете использовать this, указывающий на сам элемент внутри конструктора — вы можете столкнуться с рядом проблем, например — в случае, если совпадет несколько элементов, конструктор будет указывать непонятно на что.
В-третьих — не глядя — вы не знаете вертикальный порядок выполнения и не способны управлять им. Что произойдет, если, например, у вас будет

<div render-handlebars-template>
  <div insert-video-element='http://some.url'/>
</div>


MutationObserver корректно разруливает подобные ситуации, так как добавляется всегда одна или более нод в списке, одновременного добавления нод с большей и меньшей вложенностью не бывает.

В-четвертых, у вас явно проблемы с событиями, вы их делали «под себя».
Нет и не предусмотрено получения чего-то вроде event.target.className, например.

Ну и в-пятых, идея далеко не нова. X-tags, Polymer. Да и директивы AngularJS (которому уже не первый год) работают по тому же принципу.
Мне продолжать?
0
chemerisuk, #
Не нужно продолжать, лучше перечитайте статью. Все ваши аргументы — это заблуждения, которые свидетельствуют, что вы не усвоили материал.

Еще раз повторю — better-dom не о кастомных тэгах, и в этом состоит основное различие подхода, который продвигается x-tags, polymer и т.д. Прочтите о декораторах (не custom tags) из Web Components по ссылке из статьи, они решают схожую проблему.
0
Jabher, #
Это не заблуждения, это опыт, который я реально получил от создания аналогичной библиотеки и использования ее в течении приблизительно с июля этого года внутри собственной команды и в нескольких дружественных.

Подход «один тэг — один инициализатор» верен и в таких условиях единственно правилен, так как ты никогда не знаешь, какая из команд отработает первой. Собственный недавний горький пример научил меня этому: на один селектор были навешаны подгрузка контента (естественно, с заменой innerHTML) и создание скроллбокса. В итоге скроллбокс заменялся контентом, и отладить это было практически нереально.
То же самое с порядком выполнения: если внутри шаблона объявлен видео-инициализатор, вполне возможно, что он отработает первым, и на выходе будет некорректно сформированный тэг video, который уже в дальнейшем не сможет корректно отшаблонизироваться. Подход с @keyframes может казаться удобным, но он лишает вас возможности управлять этим сценарием.
Передавать элемент в качестве this в подобных условиях неразумно: можно создать два совпадающих инициализатора, в которых будет подменяться onMouseOver, например. В итоге будет работать только одно из событий. В моем случае это разруливалось через создание constructor.call(node[directiveName] = Object.freeze(constructor), node, arguments).
Кстати, совсем забыл: у вас нет деструкторов. Это окажется большой проблемой для всех тех элементов, где используется setInterval или requestAnimationFrame. О просторе полета в элементах с window.onresize я молчу.

Еще раз — мне продолжать тыкать в ваши ошибки, которые в итоге затруднят отладку и сделают использование библиотеки сложным или невозможным в многих сложных случаях, или вы задумаетесь, наконец, о том, что не просто так именно к элементам, а не к селекторам присматриваются Mozilla и Google?

Мне весь этот подход, который наблюдается в better-dom, напоминает завывания начинающих разработчиков, которые искренне убеждены, что краткость и чистота кода куда важнее, чем статическая типизация, например, и в панике убегают при виде что Dart или TypeScript, которые на самом деле js с обвесом, что Scala.

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