Delayed DOM Manipulations

Чебурашка решил начать разговор издалека. — Солнышко светит, травка зеленеет! — сказал он. — А нам вот так нужны гвозди! Не дадите немножко? — Это не травка зеленеет, — ответил кладовщик.- Это краску пролили. А гвоздей нет. Каждый ящик на учете.

Пожалуй эта цитата лучше всего характеризует то, что я сегодня вам приготовил. А подготовка материала как показала практика дело очень интимное. Я пару раз просил читателей сказать о чем они хотели бы почитать, но ни разу не написал, ни на одну из предложенных тем. Зато могу бросить все и начать писать о том, что мне пришло в голову как шальная мысль. Так и в этот раз, просматривая очередную заметку «100500 типс энд трикс для жуквери» я решил, что меня утомил один из этих типсов.

Самая простая вещь которая может быть это вынос за пределы цикла и отложенная работа с DOM деревом.


var lis = [1,2,3,4,5,6];
$(lis).each(function(i, e){
	$("ul#my").append("<li>"+e+"</li>")
});

Превращаеться в


var lis = [1,2,3,4,5,6], html = "";
$(lis).each(function(i, e){
	html = "<li>"+e+"</li>";
});
$("ul#my").html(html);

Кароче самая тривиальная и банальная вещь из существующих. Но писать append внутри цикла как-то логичнее и приятнее ну, по крайней мере, для меня. И решил я захачить жуквери так что бы можно было писать как хочеться а работало как надо. Вот тут собственно и уместно предисловие, пишем append внутри each и каждая миллисекунда на счету.

После дня ковыряния у меня родился вот такой код:


(function($){

/*
 * @author CTAPbIu_MABP
 * @email CTAPbIuMABP@gmail.com
 * @link http://mabp.kiev.ua/2010/08/31/delayed-dom-manipulations/
 * @license GPL
 */

	var data = {},	// тут список всех элементов и отложенных операций над ними
		isOuterLoop = true, // определяет самый внешний вложенный цикл
		supportedFunctions = [ // список переопределяемых методов
			"append",
			"prepend",
			"before",
			"after"
		];
	
	/**
	 * Складывает аргументы оригинальных методов в data
	 * @param funcName {String} имя оригинального метода 
	 * @param args {array-like object} аргументы оригинального метода
	 * @return {jQuery} возвращяет jQuery для продолжения цепочки
	 */
	function delay (funcName, args){
		var i = $.inArray(this, data[funcName].elt),
			args = $(args).toArray(); // приводим аргументы к массиву
		if (i > -1){
			data[funcName].tmp[i] = data[funcName].tmp[i].concat(args);
		}else{
			data[funcName].elt.push(this);
			data[funcName].tmp.push(args)
		}
		return this;
	}
	
	// создаем место для хранения данных
	// и обертки ко всем поддерживаемым методам 
	$(supportedFunctions).each(function(i, funcName){
		data[funcName] = {tmp : [], elt : []};
		$.fn[funcName+"Delayed"] = function(){
			return delay.call(this, funcName, arguments)
		}
	});
	
	// создаем обертку для each
	$.fn.eachDelayed = function(){
		var ret, isOuter = isOuterLoop;
		isOuterLoop = false;
		ret = $.fn.each.apply(this, arguments); // выполняем оригинальный each
		if (isOuter){ // после самого вернего вложенного цикла
			$.each(data, function(funcName, funcParam){ // обходим все данные
				$(funcParam.elt).each(function(i){ // и выполняем все оригинальные методы
					$.fn[funcName].apply(funcParam.elt[i], funcParam.tmp[i]);
				});
				data[funcName] = {tmp : [], elt : []}; // обнуляем данные 
			})
			isOuterLoop = true;
		}
		return ret;
	}
	
})(jQuery);

Ну что ж погнали, посмотрим что он делает. А делает он обертку для методов из массива supportedFunctions, грубо говоря AOP. На самом деле переопределенные методы не выполняют вставку в DOM дерево, а только вызывают функцию delayed которая складывает аргументы оригинальных методов в массив data. После конца цикла все накопленные данные проверяются и выполняются все отложенные методы.

Насколько я проверил это все прекрасно работает с несколькими вставками одновременно, во вложенных циклах и несколькими аргументами, а так же со всем этим вместе.

Теперь я думаю надо объяснить на чем основан хак. Так сложилось что все четыре метода из массива supportedFunctions используют метод domManip (line 4263) -> buildFragment (line 4355) -> clean (line 4417) передавая все свои аргументы, для построения DocumentFragment одним махом. Вот этим-то я и воспользовался, в самом внешнем цикле я один раз вызываю оригинальный метод с кучей параметров.

Еще два слова о том как пользоваться этим чудом


$(document).ready(function(){
	var lis = [1,2,3,4,5,6],
            ul1 = $("ul#my1"),
            ul2 = $("ul#my2");
	
	$(lis).eachDelayed(function(i, e){
		ul1.beforeDelayed("<div>"+e+"</div>");
		ul1.appendDelayed("<li>"+e+"</li>");
		ul2.prependDelayed("<li>1+"+e+"</li>", "<li>e+"+e+"</li>");
		$(lis).eachDelayed(function(i, e){
			ul2.appendDelayed("<li>2+"+e+"</li>");
		});
	});
	
});

Результат выполнения кода приводить не буду, там ничего красивого, зато работает. Все методы before, append и prepend выполняются по одному разу при окончании внешнего цикла.

Хотел еще сделать поддержку для всяких wrap, но что-то там как-то грустно все, они не используют domManip.

Скачать можно тут DOWNLOAD LINK

2 Комментарии “Delayed DOM Manipulations

  1. и риальне прирост производительности есть или это байтдроч очередной?

  2. не байтодроча нет. это только для личного удобства, хочешь можешь писать так как во втором примере кода написано, хочешь можешь через delayed

Комментарии закрыты