Каникулы продолжаются и мы воспользуемся этим для получения новых знаний, укрепления и расширения старых.
Долго думал, что же разобрать дальше — атрибуты, свойства и данные или манипуляцию с 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
- Атрибуты, свойства, данные