Продолжаем дело первой статьи и пытаемся разобраться с тем, что же делает за нас jQuery, когда мы с помощью этой библиотеки создаем DOM-элементы.
В прошлом выпуске мы упомянули, что при передаче в jQuery вместо селектора html-строки, на основе нее функция parseHTML создаст соответствующие элементы и вернет их в привычном jQuery-объекте. Сейчас мы рассмотрим все это более тщательно и затронем кроме core.js еще manipulation.js и attributes.js (мельком).
Начнем с простого
jQuery определяет, что вместо селектора передана html-строка по первому и последнему символу (знак «меньше» и «больше», открывающие и закрывающие тег) или, если первая проверка не удалась, по специальной регулярке /^(?:[^#<]*(<[wW]+>)[^>]*$|#([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);
} );
(в цикле создавать кучу DOM-элементов добавлять на страницу не ок и лучше пропустить это дело через дополнительный фрагмент, спасибо за замечание eforce, сам я даже не обратил внимания, к сожалению)
jQuery.fragments
в этом случае будет просто пустой, потому что для простого тега будет вызван document.createElement('span')
, на него будет повешен идентификатор и title, а внутрь — заброшен текст.
Я ни в коем случае не призываю писать только такой код, в результате которого ничего не попадает в кеш jQuery.fragments
, я лишь призываю использовать его по назначению — для хранения кусков кода, который действительно часто будет выполняться. В случае с примером выше наверняка у этих span
будет какой-нибудь класс, к примеру «user», так что вполне разумно создавать такие плашки через $('<span class="user">')
, а дальше на него что-то навешивать. Найдите баланс.
Итак, кешированный фрагмент вернется сразу же. Если же в кеше не нашлось результата, будет создан легковесный 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 внутри) и я намеренно просто не рассматривал тот его функционал, который не используется именно в нашем случае, для создания элементов из обычной строки, а предназначен для совсем других целей. Например, самым последним шагом все созданные элементы проверяются на наличие тега <script>
, в нашем случае это не имеет никакого значения. Зачем? Узнаем в следующей серии.
Заключение
Думали, все просто? К сожалению, нет. Но мы осилили! Кстати, пока писал статью и ковырялся в исходниках, тоже нашел что-то новое :)
Пишете комментарии, критикуйте, задавайте вопросы — на все постараюсь ответить.
Пишите красивый код и получайте от этого удовольствие, мальчики и девочки.
Содержание цикла статей
1. Введение
2. Парсинг html
Автор: return