Главная > CSS, HTML, JavaScript, Программирование > Ускоряем вставку в DOM дерево

Ускоряем вставку в DOM дерево

19 Февраль 2009

После написания прошлой статьи в котором рассказывал, как можно ускорить селекторы, объясняя внутреннее устройство jQuery, я решил написать еще один пост, в котором хочу объяснить как и почему можно ускорить вставку в DOM дерево. Я хочу углубиться в теорию того как jQuery обрабатывает переданный ей фрагмент html разметки на примере создания простого дива.

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


document.createElement("div");

но сначала нужно убедиться что мы хотим создать именно пустой див


// вызываем jQuery с селектором (а точнее с фрагментом html кода), без контекста
$("<div/>");

дальше идет общая часть для обоих примеров, мы получаем наш текст в переменную string и пытаемся определить что же получили


// проверяем что мы получили html а не селектор
var html = /^[^<]*(<(.|\s)+&gt)[^>]*$|^#([\w-]+)$/.exec(string)
if(html && (html[1] || !context)){
	// это для красоты
	html = html[1];
	// проверяем что наш html просто один тэг
	var tag = /^<(\w+)\s*\/?>$/.exec(html);
	if(tag[0])
		// создаем и возвращаем новый элемент
		return document.createElement(tag[0]);
}

Было бы крайне интересно, если бы можно было передавать еще и контекст в котором нужно создать элемент, тогда можно было бы не писать .appendTo(), но к сожалению это слишком сложно и накладывает кучу ограничений.

Второй пример - сложный, нам не просто надо создать див но надо добавить к нему свойство style и текст


// вызываем jQuery
$("<div style='position:absolute;'>text</div>");

Вторая проверка из прошлого фрагмента кода не пройдена и мы должны уточнить, что же надо создать, попутно создаем вспомогательный див который будет служить нам оберткой


var div = context.createElement("div");

Приводим html к виду xhtml, это значит что если бы у нас была строка
<div style='position:absolute;'/>
то она превратилась бы в
<div style='position:absolute;'></div>
это не относиться к перечисленым тэгам


html = html.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
	return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
		all :
		front + "></" + tag + ">";
});

Затем нужно проверить целостность html'a если мы пытаемся создать элементы которые изначально должны быть вложены в другие, то их следует сначала вложить а потом создать.


var wrap =
	!tags.indexOf("<opt") &&
	[ 1, "<select multiple='multiple'>", "</select>" ] ||

	!tags.indexOf("<leg") &&
	[ 1, "<fieldset>", "</fieldset>" ] ||

	tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
	[ 1, "<table>", "</table>" ] ||

	!tags.indexOf("<tr") &&
	[ 2, "<table><tbody>", "</tbody></table>" ] ||

	(!tags.indexOf("<td") || !tags.indexOf("<th")) &&
	[ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||

	!tags.indexOf("<col") &&
	[ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||

	// IE не может сериализировать <link> и <script> тэги нормально
	!jQuery.support.htmlSerialize &&
	[ 1, "div<div>", "</div>" ] ||

	[ 0, "", "" ];

div.innerHTML = wrap[1] + elem + wrap[2];

Ну и естественно обертки нам не нужны, мы их отбрасываем


while ( wrap[0]-- )
	div = div.lastChild;

Если у нас было несколько элементов верхнего уровня
<div>text1</div><div>text2</div>
мы бы это все еще разбили в массив, но так как мы этого не знаем и для однородности отдачи мы все равно это делаем


array = Array.prototype.push.apply([], div.lastChild);

Статья изначально предполагалась для объяснения, почему для манипуляций с единичными элементами DOM лучше создавать пустые элементы, а потом методами jQuery добавлять им свойства.


$("<div/>").text("text")
// лучше чем ?
$("<div>text</div>")

Но в ходе написания статьи было обнаружены интересные подробности: оказывается первый вариант выполняется, чуть более чем, вдвое дольше второго


var start = new Date();
for(var i=0;i<1000;i++)
	$("<div/>").text("text")
var stop = new Date();
alert(stop-start) // 280

var start = new Date();
for(var i=0;i<1000;i++)
	$("<div>text</div>")
var stop = new Date();
alert(stop-start) // 125

Я очень сильно удивился и посмотрел код метода .text() он попутно успевает вызвать метод .empty(), который очень широким жестом удаляет всех потомков текущей ноды. Теория начала с треском проваливаться, но не все методы используют .empty() и я составил табличку, какой метод, сколько времени выполняеться и картина снова стала меня радовать.

$("<div>text</div>") 125
$("<div/>").text("text") 280
$("<div/>").html("text") 350
$("<div attribute='value'/>") 140
$("<div/>").css({property:value}) 100
$("<div/>").attr({attribute:value}) 95
$("<div/>").addClass("myClass") 75
$("<input/>").val("myValue") 70

Хочу обратить ваше внимание на то что .css() это частный случай для .attr() поэтому работает чуть дольше, но лучше отображает суть происходящего.

Эксперимент доказал, что если требуеться создавать пустой элемент (по большей части это касаеться дивов), то всетаки лучше параметры передавать потом в методах. Это экономит до 50% времени.

ЗЫ: Я уже знаю что в каментах будут писать о том, что это экономия на спичках, но эта статья из большой и чистой теории и я никого не принуждаю так делать - делайте как удобнее.

  1. NRG
    20 Февраль 2009 в 10:25 | #1
    пасиба Маврыся, хорошая статья. И кстати экономия на спичках это как с миру по нитке, походу везде почутьчуть спичек секономим а в итоге получим вери гуд призводительность
  2. Имя
    12 Июль 2010 в 08:27 | #2
    $("").html("text")== $("#id_div")[0].innerHTML="text" Как-то так
  3. 14 Июль 2010 в 21:00 | #3
    @Имя полагаю както так не очень красиво, да и вообще статья уже не актуальна)
Комментирование отключено.