Hello, Kitty!
posted in JavaScript, Web Dev. |
В целях быстрой прокачки скиллов клиент-сайд программирования начал писать собственный JavaScript фреймворк... с блекждеком и... ну, вы поняли...
Встречайте, Kitty.
При разработке, в основном, ориентировался на jQuery, как самый распространенный, и, пожалуй, самый простой фреймворк.
Поэтому в плане интерфейса Kitty будет похож на jQuery, а где-то будет повторять его.
Часть 1: поисковый движок.
Первой целью, естественно, было создание поискового движка, позволяющего быстро выбрать все элементы страницы по заданной строке в CSS-совместимом формате (селекторы):
TR#id.class[attr=value]:flag TR#id.class[attr="value"]:flag, TR#id.class[attr='value']:flag
Например:
DIV#myDiv td.evenRow input[type=radio]:checked, DIV#myDiv2 input[type=checkbox]
выборка по этому сложному селектору должна вернуть массив элементов, состоящих из выделенных радиобаттонов, которые находятся в ячейках таблицы, имеющих класс evenRow и находящихся внутри дива с id=myDiv, а так же из чекбоксов, находящихся внутри дива с id=myDiv2.
Для начала строка приводится к правильному виду - удаляются лишние пробелы.
// множество пробелов заменяем одним
selectionStr = selectionStr.replace(/\s+/g," ");
// trim - удаляем пробелы в начале и в конце строки
selectionStr = selectionStr.replace(/^\s|\s$/g,"");
// удаляем пробелы вокруг запятых и лишние запятые
selectionStr = selectionStr.replace(/\s\,/g,",").replace(/\,\s/g,",").replace(/^\,+|\,+$/g,"");
Полученную строку разрезаем по запятым на несколько различных селекторов, результаты которых по завершению всех выборок будут склеены.
Более сложная задача - выборка элементов с учетом иерархии DOM.
Для парсинга отдельных частей сложных селекторов было составлено вот такое регулярное выражение:
/^([*A-Za-z]+)?(\#[A-Za-z\_\-0-9]+)?(\.[A-Za-z\_\-0-9]+)?(\[([A-Za-z\_\-0-9]+)\=["']?([A-Za-z\_\-0-9]+)["']?\])?(\:[A-Za-z\_\-0-9]+)?/i
А так же была написана простейшая функция фильтрации набора нод по заданному простому селектору.
Первой идеей было спускаться по выборке сверху вниз:
т.е. в случае селектора DIV#myDiv td.evenRow input[type=radio]:checked, сначала получаем все ноды страницы, отыскиваем среди них DIV c id=myDiv, получаем все дочерние ноды, ищем среди них td c классом evenRow, получаем их дочерние элементы и среди них выбираем искомые радиобаттоны.
Реализация в 5 строк кода, однако возникло несколько проблем.
Во первых, одна и та же нода могла быть выбрана несколько раз: например, в случае "div div div", приходилось запоминать в отдельный массив, какая нода уже попала в выборку, а какая - нет.
Во вторых, производительность оказалась очень низкой. Цикл из 10 000 выборок работал более 17 секунд.
Остановился на более сложном, но и более быстром алгоритме "снизу вверх":
Сначала на странице находим все элементы, которые удовлетворяют конечным (например, радиобаттоны).
Затем пробегаемся по ним циклом, и для каждого проверяем удовлетворяет ли цепочка их родителей остальной части селектора. При этом, если родители одной ноды удовлетворяют условиям поиска, то другие найденные ноды, с тем же родителем автоматически считаются искомыми. И наоборот, если родители ноды не удовлетворяют селектору, то ее соседки так же исключаются из результатов.
Костяк поискового движка уложился в 100 строк кода, и совсем немного уступает по скорости движку Sizzle, который используется в jQuery - на 10000 сложных выборок - Kitty уступал в скорости jQuery 150-200 мс. При этом, выигрывал 30-70 мс на простых выборках.
Конечно, функционально поисковый Kitty еще заметно уступает Sizzle: не реализована выборка по специальным селекторам вроде :first, :last, :checked и т.п., нет поддержки дочерних элементов вида "a > b", однако, 90% моих потребностей он уже покрывает.
Подключение и работа:
<script type="text/javascript" src="kitty.js"></script>
После подключения доступен объект Kitty и короткий алиас $K для выборки элементов.
Пример:
<script>
Kitty.debug = true;
$K("div#div1 li#li2, table li").style('color', 'red');
$K("td ul").style('background-color', 'yellow');
<script>
Ссылка для скачивания:
to be continued...