Каникулы продолжаются и мы воспользуемся этим для получения новых знаний, укрепления и расширения старых.
Долго думал, что же разобрать дальше — атрибуты, свойства и данные или манипуляцию с DOM, даже начал писать обе статьи. И вроде бы сначала хорошо было бы дать первую тему, но в комментариях к предыдущему топику уже обратили внимание на одну особенность работы со скриптами, которая как раз относится ко второй теме, так что не будем тянуть и начнем именно с нее. Заодно прошу прощения у тех, кто увидел начало статьи, которое я по ошибке опубликовал в процессе написания.
Итак, сегодня мы продолжим серию копаний в исходниках jQuery под номером 1.8.3 (стабильная версия на момент написания статьи). Общее представление о jQuery мы уже получили, парсить html — тоже. Пора то, что мы распарсили куда-нибудь вставить.
Практически любые работы с DOM так или иначе идут в jQuery через функцию domManip. Рассматривать все функции, с помощью которых можно менять DOM в библиотеке, мы не будем. Рассмотрим только базовые.
Цель этой служебной функции — выполнить определенный callback к каждому элементу, но при этом выполнить еще кучу дополнительной работы.
Дабы не рассказывать о
Если быть кратким, то в каждый элемент (если он — обычный
Внутри функции строится фрагмент (знакомыми нам по предыдущей статье функциями buildFragment и clean), при этом внутри
Дальше для каждого элемента в нашем jQuery-объекте в его контексте выполнится тот самый callback, на вход которому будет подан клонированный фрагмент, который мы получили выше.
Бесполезный (но наглядный, надеюсь) пример в студию:
В этом случае в начало каждого
Что же со скриптами, для чего мы собирали их отдельно? Каждый скрипт будет выполнен обычным eval. Если это
Это пример из комментариев к предыдущему топику (спасибо alisey). Обратите внимание, что созданный
Тут же кроется и подвох со вставкой скриптов с атрибутом
В этом случае такой код — плохой, не делайте так:
А вот так — можно, да здравствует нативный Javascript:
Эти функции работают мимо
Эта функция в случае, если при вызове не указано ни одного параметра, возвращает нам содержимое нашего тега, значение его свойства
В случае, если же указан параметр, во все ноды нашего jQuery-объекта библиотека в большинстве случаев (опять же, если в коде нет тегов
appendTo, prependTo, insertBefore, insertAfter, replaceAll. Что общего между ними? Они, по сути, являются алиасами к другим функциям, просто меняют местами то, к чему применяют функцию, с ее параметром. Говоря проще, происходит что-то вроде этого:
Сумбурно получилось в этот раз как-то, да? Я мог что-то упустить. В любом случае пишите, если есть вопросы, если я что-то упустил или даже наврал.
Пишите код и получайте от этого удовольствие!
Долго думал, что же разобрать дальше — атрибуты, свойства и данные или манипуляцию с DOM, даже начал писать обе статьи. И вроде бы сначала хорошо было бы дать первую тему, но в комментариях к предыдущему топику уже обратили внимание на одну особенность работы со скриптами, которая как раз относится ко второй теме, так что не будем тянуть и начнем именно с нее. Заодно прошу прощения у тех, кто увидел начало статьи, которое я по ошибке опубликовал в процессе написания.
Итак, сегодня мы продолжим серию копаний в исходниках jQuery под номером 1.8.3 (стабильная версия на момент написания статьи). Общее представление о jQuery мы уже получили, парсить html — тоже. Пора то, что мы распарсили куда-нибудь вставить.
Практически любые работы с DOM так или иначе идут в jQuery через функцию domManip. Рассматривать все функции, с помощью которых можно менять DOM в библиотеке, мы не будем. Рассмотрим только базовые.
domManip
Цель этой служебной функции — выполнить определенный callback к каждому элементу, но при этом выполнить еще кучу дополнительной работы.
Дабы не рассказывать о
domManip
безсвязно, вот весь код функции append:append: function() {
return this.domManip(arguments, true, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 ) {
this.appendChild( elem );
}
} );
},
Если быть кратким, то в каждый элемент (если он — обычный
ELEMENT_NODE
или DOCUMENT_FRAGMENT_NODE
) нашего набора в текущем jQuery-объекте будет через обычный appendChild
добавлен элемент из параметров к функции.Внутри функции строится фрагмент (знакомыми нам по предыдущей статье функциями buildFragment и clean), при этом внутри
clean
дополнительно происходит сбор всех скриптов, которые в результирующий фрагмент не попадут, а вернутся в отдельном массиве.Дальше для каждого элемента в нашем jQuery-объекте в его контексте выполнится тот самый callback, на вход которому будет подан клонированный фрагмент, который мы получили выше.
Функция как параметр
domManip
(соответственно, и функции, использующие его) умеют принимать на свой вход функцию, в этом случае она будет для каждого элемента в jQuery-объекте вызвана в его контексте для получения того, что мы хотим в нее передать.Бесполезный (но наглядный, надеюсь) пример в студию:
<span class="user" data-id="15">Игорь</span>
<span class="user" data-id="10">Дарья</span>
<script src="http://code.jquery.com/jquery-1.8.3.js"></script>
<script>
// вообще такой селектор использовать - не очень ок, просто так нагляднее
$('span.user').prepend( function(idx, html) {
// попутно в функцию передаются порядковый номер и содержимое тега,
// к которому применяется функция
console.log(idx, html);
// о $.data мы поговорим в следующей статье
return $(this).data('id') + ': ';
} );
</script>
В этом случае в начало каждого
span
с классом «user» будет добавлена текстовая нода, в содержимом которой будет идентификатор пользователя:jQuery.fn.prepend = function() {
// в результате нас вернется все тот же набор ссылок на наши span'ы в jQuery-объекте
return this.domManip(arguments, true, function( elem ) {
// функция будет вызвана два раза
// для каждого span.user, в его контексте (this)
// 1 -> ELEMENT_NODE
// 11 -> DOCUMENT_FRAGMENT_NODE
// под это условие наши span'ы подходят
if ( this.nodeType === 1 || this.nodeType === 11 ) {
// вставить elem (в этом случае - текстовая нода вида "xx: ")
// перед первым тегом внутри span (тоже текстовая нода, с именем пользователя)
this.insertBefore( elem, this.firstChild );
}
} );
};
Скрипты
Что же со скриптами, для чего мы собирали их отдельно? Каждый скрипт будет выполнен обычным eval. Если это
script
с атрибутом src
, то он будет предварительно синхронно (дабы соблюсти порядок выполнения скриптов) загружен. В DOM такие скрипты не попадут.$('<div>').append('<script>alert(1);</script>')
Это пример из комментариев к предыдущему топику (спасибо alisey). Обратите внимание, что созданный
div
висит в воздухе, в документе его нет, однако скрипт в этом случае все равно будет выполнен.Тут же кроется и подвох со вставкой скриптов с атрибутом
src
. То же самое с jQuery.getScript и jQuery.ajax с типом script
(все это — аналоги). По-умолчанию jQuery считает что такое не нужно кешировать и к урлу при загрузке добавляет параметр с текущим unix-timestamp. В этом случае если браузер захочет закешировать ответ от сервера (все зависит от заголовков), то закеширует его по урлу, где будет timestamp, что в некоторых (скорее даже в большинстве) случаев — не приемлемо, потому что следующий подобный запрос опять пойдет мимо кеша и опять будет закеширован с timestamp'ом в url'е.В этом случае такой код — плохой, не делайте так:
$('<script>', {
'src': 'http://code.jquery.com/jquery-1.8.3.min.js'
} ).appendTo(document.body);
А вот так — можно, да здравствует нативный Javascript:
var
scriptElement = document.createElement('script');
scriptElement.setAttribute('src', 'http://code.jquery.com/jquery-1.8.3.min.js');
document.body.appendChild(scriptElement);
jQuery.empty и jQuery.remove
Эти функции работают мимо
domManip
и занимаются удалением элементов из дерева через обычный removeChild. jQuery.empty
удаляет все вложенные элементы жертвы, а jQuery.remove
удаляет только те ноды, которые подходят под селектор, указанный в параметре к вызову этой функции. Оба метода вызывают пока не известную нам функцию cleanData, работу которого мы затронем в следующей части.jQuery.html
Эта функция в случае, если при вызове не указано ни одного параметра, возвращает нам содержимое нашего тега, значение его свойства
innerHTML
, из которого будут предварительно удалены служебные атрибуты jQuery.В случае, если же указан параметр, во все ноды нашего jQuery-объекта библиотека в большинстве случаев (опять же, если в коде нет тегов
script
, style
, link
и первый тег не встречается в знакомом нам по предыдущей статье wrapMap
) попробует задать свойство innerHTML
напрямую, иначе — сначала очистит содержимое нашего тега с помощью empty
, а затем добавит в него код с помощью append
.Немного алиасов напоследок
appendTo, prependTo, insertBefore, insertAfter, replaceAll. Что общего между ними? Они, по сути, являются алиасами к другим функциям, просто меняют местами то, к чему применяют функцию, с ее параметром. Говоря проще, происходит что-то вроде этого:
$('<span>').appendTo(document.body) => $(document.body).append('<span>')
$('span.user').insertAfter('span:first') => $('span:first').after('span.user')
Заключение
Сумбурно получилось в этот раз как-то, да? Я мог что-то упустить. В любом случае пишите, если есть вопросы, если я что-то упустил или даже наврал.
Пишите код и получайте от этого удовольствие!
Содержание цикла статей
- Введение
- Парсинг html
- Манипуляции с DOM
- Атрибуты, свойства, данные