jQuery изнутри, погружение (часть 2)

от автора

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

Чуть более полутыщи человек добавили первую часть в избранное и совсем не все так плохо, как казалось мне и некоторым комментаторам. За внимание к теме всем спасибо!

В этом выпуске мы продолжаем возиться с кодом последней на данный момент версии — 1.8.3.

В прошлом выпуске мы упомянули, что при передаче в jQuery вместо селектора html-строки, на основе нее функция parseHTML создаст соответствующие элементы и вернет их в привычном jQuery-объекте. Сейчас мы рассмотрим все это более тщательно и затронем кроме core.js еще manipulation.js и attributes.js.

Начнем с простого

jQuery определяет, что вместо селектора передана html-строка по первому и последнему символу (знак «меньше» и «больше», открывающие и закрывающие тег) или, если первая проверка не удалась, по специальной регулярке /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/.

ВНИМАНИЕ: начиная с версии 1.9 проверка будет состоять только из в подсматривании первого символа строки, он должен быть знаком «меньше». По крайней мере так писали в блоге. В этом случае строка "test<div>лалала" уже не будет воспринята как html, который jQuery потом будет пытаться распарсить, будьте внимательны.

Перво-наперво будет проверено, передан ли в качестве строки простой одиночный тег без какого-нибудь содержимого, в этом случае он будет создан просто через context.createElement( [tagName] ), выполнение каждой из этой строки приведет довольно быстро к одному и тому же результату:

$('<div>') $('<div />') $('<div></div>') $(document.createElement('div'))

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

Перейдем к более сложному

В остальных случаях jQuery произведет настолько больше всякой работы, что мы заранее посочувствуем старым медленным браузерам, скажем core.js «давай, до свидания!» и посмотрим на buildFragment из manipulation.js, именно там начнет вершиться магия. Вкратце — будет создаваться фрагмент, в него по-тихоньку помещаться полученные DOM-элементы, которые из него потом и придут в результат.

Кеширование

Первым делом для указанного html-кода будет определено, можно и нужно ли его кешировать. Кешировать результат построения можно, если он строится в контексте document, длина исходного html не превышает 512 символов, не содержит тегов script, object, embed, option или style и проходит несколько браузеро-специфических тестов (к примеру, в относительно несвежем Webkit’е клонировать фрагмент с нодой, у которой задан атрибут checked, не получится с сохранением его значения). Результаты, которые можно кешировать, сначала отмечаются в объект fragments, а на вторую попытку создать что-то по тому же самому html, туда уйдет и сам результат.

Тут мы встречаемся с проблемой — объект jQuery.fragments никогда не чистится! Для динамических приложений, в которых на одной странице приходится создавать много элементов по каким-то пришедшим данным, это важно. Подумайте несколько раз, прежде чем создавать тоннами какие-то простые элементы именно таким способом.

Реальный пример с созданием каких-то воображаемых плашек для хеша с тремя воображаемыми пользователями:

var     users = {         5: 'Ольга',         6: 'Вася',         10: 'Юля'     };  $.each(users, function(id, name) {     $('<span id="user' + id + '" title="Пользователь ' + name + '">' + name + '</span>')         .appendTo(document.body); } );

Код некрасив и так писать не стоит в любом случае. Тем не менее я этот код у разработчиков периодически вижу, а на волне популярности javascript-шаблонизаторов его становится все больше и больше. Что в итоге получится в jQuery.fragments:

> jQuery.fragments Object {   <span id="5" title="Пользователь Петя">Петя</span>: false   <span id="6" title="Пользователь Вася">Вася</span>: false   <span id="10" title="Пользователь Юля">Юля</span>: false }

Три span’а, три элемента в объекте jQuery.fragments. false в значении — как раз то, о чем я говорил, в первый раз они только попадают в объект, на второй раз — вместо false там будет сам результат. Несколько записей — фигня вопрос, конечно. А вот тонны записей — пустой расход памяти и никуда не годится. Можно попробовать понабирать что-нибудь в саджесте поиска на Хабре и потом в консоли глянуть на мусор в jQuery.fragments.

А вот немного другой код, но более красивый и с тем же результатом:

$.each(users, function(id, name) {     $('<span>', {         'id': 'user' + id,         'title': 'Пользователь ' + name,         'text': name     } ).appendTo(document.body); } );

jQuery.fragments в этом случае будет просто пустой, потому что для простого тега будет вызван document.createElement('span'), на него будет повешен идентификатор, а внутрь — заброшен текст.

И так, кешированный фрагмент вернется сразу же. Если же в кеше не нашлось результата, будет создан легковесный DocumentFragment и наше внимание будет переключено на функцию clean, в которую будут прокинуты свежесозданный фрагмент и наш html-код.

safeFragment

Все временные действия с созданием элементов производятся в специальном фрагменте-отстойнике, safeFragment, который создается при инициализации. Причем в IE он еще и дополнительно обрабатывается для поддержки html5-тегов (см. баг, очень интересный).

Создание элементов

В safeFragment создается пустой <div></div>, в который jQuery с помощью стандартного метода innerHTML записывает наш html-код

Но предварительно jQuery пытается найти, не нужно ли обрамить наш код как-то дополнительно. Берется самый первый найденный в коде тег и ищется в служебном объекте wrapMap. Зачем вообще что-то обрамлять? Затем, что нельзя просто взять и вставить в innerHTML у <div>, к примеру, <td>Привет!</td>:

var k = document.createElement('div'); k.innerHTML = '<td>Привет!</td>'  > k <div>​Привет!​</div>​

Для случая с <td>Привет!</td>, код превратится в <table><tbody><tr><td>Привет!</td></tr></tbory></table>, а указатель на контейнер с нужным нам результатом будет смещен на глубину в 3 тега, тоесть в <tr> вместо <div>, который был создан внутри safeFrag в самом начале.

Дальше идет постобработка результата для некоторых случаев в IE — удаление вставленных автоматически tbody в таблицы и добавление удаленных автоматически в пробелов вначале нашего года.

Все, ура, мы можем получить наш результат из контейнера с помощью стандартной функции childNodes и удалить его из safeFrag.

Пробегаемся по полученному набору нод и добавляем их в наш собственный фрагмент, который попадет в кеш (если он кешируется, см. выше) и отдается нам назад в parseHTML, йуху! Там в наш результат мержится полученный клонированный фрагмент (если получен из кеша), либо он сам.

Неужели все?

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

Заключение

Думали, все просто? К сожалению, нет. Но мы осилили! Кстати, пока писал статью и ковырялся в исходниках, тоже нашел что-то новое 🙂

Пишите красивый код и получайте от этого удовольствие, мальчики и девочки.

ссылка на оригинал статьи http://habrahabr.ru/post/164533/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *