- PVSM.RU - https://www.pvsm.ru -

jQuery изнутри — манипуляции с DOM

Каникулы продолжаются и мы воспользуемся этим для получения новых знаний, укрепления и расширения старых.

Долго думал, что же разобрать дальше — атрибуты, свойства и данные или манипуляцию с DOM. И вроде бы сначала хорошо было бы дать первую тему, но в комментариях к предыдущему топику уже обратили внимание на одну особенность работы со скриптами, которая как раз относится ко второй теме, так что не будем тянуть и начнем именно с нее. Заодно прошу прощения у тех, кто увидел начало статьи, которое я по ошибке опубликовал в процессе написания.

Итак, сегодня мы продолжим серию копаний в исходниках jQuery под номером 1.8.3 (стабильная версия на момент написания статьи). Общее представление о jQuery мы уже получили [1], парсить html — тоже [2]. Пора то, что мы распарсили куда-нибудь вставить.

Практически любые работы с DOM идут в jQuery через функцию domManip [3].

domManip

Цель этой служебной функции — выполнить определенный callback к каждому элементу, но при этом выполнить еще кучу дополнительной работы.

Дабы не рассказывать о domManip безсвязно, вот весь код функции append [4]:

	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 добавлен элемент из параметров к функции.

Внутри функции строится фрагмент [5] (знакомами нам по предыдущей статье функциями buildFragment [6] и clean [7]), при этом внутри clean дополнительно происходит сбор всех скриптов, которые в результирующий фрагмент не попадут, а вернутся в отдельном массиве.

Дальше для каждого элемента в нашем jQuery-объекте в его контексте выполнится тот самый callback [8], на вход которому будет подан клонированный фрагмент, который мы получили выше.

Функция как параметр

domManip (соответственно, и функции, использующие его) умеют принимать на свой вход функцию, в этом случае она будет для каждого элемента в jQuery-объекте вызвана [9] в его контексте для получения того, что мы хотим в нее передать.

Бесполезный пример в студию:

<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» будет добавлена текстовая нода, в содержимом которой будет идентификатор пользователя.

Скрипты

Что же со скриптами, для чего мы собирали их отдельно? Каждый скрипт [10] будет предварительно загружен (если это script с тегом src) и выполнен обычным eval [11]. В DOM такие скрипты не попадут.

$('<div>').append('<script>alert(1);</script>')

Это пример из комментариев к предыдущему топику (спасибо alisey [12]). Обратите внимание, что созданный div висит в воздухе, в документе его нет, однако скрипт в этом случае все равно будет выполнен.

Тут же кроется и подвох со вставкой скриптов с атрибутом src. То же самое с jQuery.getScript [13] и jQuery.ajax [14] с типом 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 [15]. jQuery.empty удаляет все вложенные элементы жертвы, а jQuery.remove удаляет только те ноды, которые подходят под селектор, указанный в параметре к вызову этой функции. Оба метода вызывают пока не известную нам функцию cleanData [16], работу которого мы затронем в следующей части.

jQuery.html

Эта функция в случае, если при вызове не указано ни одного параметра, возвращает [17] нам содержимое нашего тега, значение его свойства innerHTML, из которого будут предварительно удалены служебные атрибуты атрибуты jQuery.

В случае, если же указан параметр, во все ноды нашего jQuery-объекта библиотека в большинстве случаев [18] попробует задать свойство innerHTML напрямую [19], иначе — сначала очистит содержимое нашего тега с помощью empty, а затем добавит в него код с помощью append.

Немного алиасов напоследок

appendTo [20], prependTo [21], insertBefore [22], insertAfter [23], replaceAll [24]. Что общего между ними? Они, по сути, являются алиасами [25] к другим функциям, просто меняют местами то, к чему применяют функцию, с ее параметром. Говоря проще, происходит что-то вроде этого:

$('<span>').appendTo(document.body) => $(document.body).append('<span>')
$('span.user').insertAfter('span:first') => $('span:first').after('span.user')

Заключение

Сумбурно получилось в этот раз как-то, да? Я мог что-то упустить. В любом случае пишите, если есть вопросы, если я что-то упустил или даже наврал.

Пишите код и получайте от этого удовольствие!

Содержание цикла статей

1. Введение [1]
2. Парсинг html [2]
3. Манипуляции с DOM

Автор: return

Источник [26]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/24121

Ссылки в тексте:

[1] уже получили: http://habrahabr.ru/post/164433/

[2] тоже: http://habrahabr.ru/post/164533/

[3] domManip: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L299

[4] append: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L128

[5] строится фрагмент: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L326

[6] buildFragment: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L487

[7] clean: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L640

[8] выполнится тот самый callback: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L341

[9] вызвана: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L318

[10] Каждый скрипт: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L356

[11] eval: https://github.com/jquery/jquery/blob/1.8.3/src/core.js#L550

[12] alisey: http://habrahabr.ru/users/alisey/

[13] jQuery.getScript: http://api.jquery.com/jQuery.getScript/

[14] jQuery.ajax: http://api.jquery.com/jQuery.ajax/

[15] removeChild: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L203

[16] cleanData: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L774

[17] возвращает: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L226

[18] в большинстве случаев: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L232

[19] напрямую: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L245

[20] appendTo: http://api.jquery.com/appendTo/

[21] prependTo: http://api.jquery.com/prependTo/

[22] insertBefore: http://api.jquery.com/insertBefore/

[23] insertAfter: http://api.jquery.com/insertAfter/

[24] replaceAll: http://api.jquery.com/replaceAll/

[25] являются алиасами: https://github.com/jquery/jquery/blob/1.8.3/src/manipulation.js#L530

[26] Источник: http://habrahabr.ru/post/164677/