jsLib: базовые функции

07.04.2008 8 JavaScript jsLib

$

Для выборки элементов из DOM я практически всегда использую выборку по ID. Поэтому, для увеличения продуктивности, считаю оправданным дополнительный вызов функции обертки. Кроме того, имя этой функции используется в качестве namespace для базовых функций.

/**
* getElementById
* @param {String} id
* @return {HTMLElement}
*/
function $(id)
{
	return document.getElementById(id);
}

Изначально эта фукциия была умнее и проверяла типы и количество передаваемых параметров (а-ля prototype.$). Однако практика (и статистика) показала, что эти возможности я практически никогда не использовал, и они были выкинуты.

$ + cache

Для перегруженных приложений иногда использую кэширующую версию.

/**
* getElementById
* @param {String} id
* @param {Boolean} notCached don't take it from cache
* @return {HTMLElement}
*/
function $(id, notCached)
{
	return !notCached && $.cache[id] || ($.cache[id]=document.getElementById(id));
}
$.cache={};

Однако заметного прироста скорости не видно. Наверное, потому, что чаще всего я кэширую элементы в локальных переменных (хорошая привычка). Да и 1000 звездочек я не перекрашиваю ;) - считаю такие ситуации просчетом архитектуры.

$.ie

Чаще всего для обеспечения кроссбраузерности всевозможные хитрости приходится использовать для IE. Поэтому я вынес проверку этого (и только этого) браузера в самое начало списка необходимых функций. Тем более, что она простая и 100% работающая.

/**
* IE detect
*/
$.ie=/*@cc_on!@*/false;
/*@if(@_jscript_version>=5.7) $.ie=7; @end@*/

Исправление мерцающих фоновых картинок, опять-таки для IE.

// fix IE background image filckering
if($.ie)
	try{
		document.execCommand('BackgroundImageCache', false, true);
	}catch(e){}

$.extend

Для наследования объектов js использую функцию практически один в один повторяющую реализацию наследования в ExtJS. Обещаю чуть позже написать статейку про наследование в js и сравнение его наиболее популярных реализаций.

/**
* Extends one class with another class and optionally overrides members with the passed literal.
* @param {Object} subclass The class inheriting the functionality
* @param {Object} superclass The class being extended
* @param {Object} overrides (optional) A literal with members
* @return {Function} subclass (constructor for creating subclass objects)
*/
$.extend=function(subclass,superclass,overrides)
{
	if(typeof(superclass)=='object'){
		overrides=superclass;
		superclass=subclass;
		subclass=function(){ superclass.apply(this,arguments); };
	}
	var F=function(){};
	F.prototype=superclass.prototype;
	subclass.prototype=new F();
	subclass.prototype.constructor=subclass;
	subclass.superclass=superclass.prototype;
	if(superclass.prototype.constructor==Object.prototype.constructor)
		superclass.prototype.constructor=superclass;
	for(var i in overrides)
		subclass.prototype[i]=overrides[i];
	return subclass;
};

$.apply, $.applyIf

Одним из наиболее привлекательных подходов в ExtJS я считаю ее способ передачи параметров. Параметр передается один - объект (ассоциативный массив) с настройками. Это обеспечивает наглядность и расширяемость.

/**
* Copies all the properties of src to dst.
* @param {Object} dst The receiver of the properties
* @param {Object} src The source of the properties
*/
$.apply=function(dst,src,defaults)
{
	if(defaults)
	for(var i in defaults)
		dst[i]=defaults[i];

	for(var i in src)
		dst[i]=src[i];
};

/**
* Copies all the properties of src to dst if they don't already exist.
* @param {Object} dst The receiver of the properties
* @param {Object} src The source of the properties
*/
$.applyIf=function(dst,src)
{
	for(var i in src)
		if(!(i in dst))
			dst[i]=src[i];
};

$.tpl

Простейший обработчик шаблонов на js не понятно как сюда затесался (т.к. используется не часто), но он тут есть :)

/**
* Simple JS-HTML template
* @param {String} tpl template string (params as {param})
* @param {Object} dict object whoes properties are used as params
* @return {String} returns applied template
*/
$.tpl=function(tpl,dict)
{
	return tpl.replace(
		/\{([\w-]+)\}/g,
		function(str,key){
			return dict[key]||(typeof(dict[key])=='number'?0:'');
		}
	);

$.bind

Основопологающей особенностью js являются замыкания. Вот моя реализация функции упрощающей их создание.

/**
* Bind function to context
* @param {Object|HTMLElement} context
* @param {Function} fn
* @return {Function} function bound to context
*/
$.bind=function(context, fn /*args...*/)
{
	if(typeof(context)=='function'){
		var args=Array.prototype.slice.call(arguments,0);
		args.unshift(null);
		return $.bind.apply(this, args);
	}

	if(arguments.length==2){	// params on call
		return function(){
			fn.apply(context||null, arguments);
		};
	}else{	// params on create
		var args=Array.prototype.slice.call(arguments,2);
		return function(){
			fn.apply(context||null, args.concat(Array.prototype.slice.call(arguments,0)));
		};
	}
};

$$

Иногда пользуюсь выборкой элементов из DOM по имени класса. Ниже представлена функция, реализующая наиболее быстрый способ перебора элементов в зависимости от поддерживаемых браузером функций.

/**
* getElementsByClassName
* @param {String} className	CSS class to find (without dot)
* @param {String} nodeName	DOM nodeName to find
* @param {HTMLElement} parentElement	root of DOM subtree to scan
* @return {Array} returns array of HTMLElement`s
*/
var $$=(function()
{
	// native: FF3, Safari
	if(document.getElementsByClassName)
	return function(className, nodeName, parentElement)
	{
		var col=(parentElement||document).getElementsByClassName(className);
		col=Array.prototype.slice.call(col, 0);

		if(nodeName && nodeName !='*'){	// filter by nodeName
			//!todo FF3 test
			for(var i=0; i<col.length; i++)	//! no n=col.length optimization: col.splice used!
				if(col[i].nodeName!=nodeName)
					col.splice(i--,1);
			// col=Array.filter(col, function(el){ return el.nodeName==nodeName; });	// fail in Safari
		}

		return col;
	};

	// xpath: FF2, Op
	if(document.evaluate)
	return function(className, nodeName, parentElement)
	{
		var s=document.evaluate(
			".//" + (nodeName||'*') + "[contains(concat(' ', @class, ' '), ' " + className + " ')]",
			(parentElement||document),
			null,
			XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
			null
		);

		var col=[], i=0, el;

		while((el=s.snapshotItem(i)))
			col[i++]=el;

		return col;
	};

	// generic: IE
	return function(className, nodeName, parentElement)
	{
		var s=(parentElement||document).getElementsByTagName(nodeName||'*');

		var col=[], i=0, el, cl;
		var re=new RegExp('(^|\\s)' + className + '(\\s|$)');

		while((el=s[i++]))
			if((cl=el.className) && (cl==className || re.test(cl)))
				col[col.length]=el;

		//!todo speed test in IE
		// for(var i=0, n=col.length; i<n; i++)
			// if((cl=col[i].className) && (cl==className || re.test(cl)))
				// col[col.length]=el;

		return col;
	};
})();

toggle

Очень часто используется скрытие/отображение элемента.

/**
* Toggle initially hidden block
* @param {String} id
*/
function toggle(id)
{
	var o=($(id)||{}).style;
	if(o) o.display ? o.display='' : o.display='block';
}

preloadImages

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

/**
* Preload images
* @param {String} ... images to load
*/
function preloadImages(/*args...*/)
{
	if(!document.images) return;

	if(!document.imageArray) document.imageArray=[];
	var j=document.imageArray.length;

	for(var i=0; i<arguments.length; i++)
		if(arguments[i].indexOf('#')!=0){
			document.imageArray[j]=new Image;
			document.imageArray[j++].src=arguments[i];
		}
}

Комментарии приветствуются.



Комментарии

qoqenator 24.04.2008 11:49

Почему не jQuery? Тем более очень похоже.

dpp 24.04.2008 11:48

Разве?
Если честно - я не понимаю идеи jQuery. Основной упор у нее на сложные выборки элементов и цепочки вызовов функций. У меня подобных задач не возникает практически никогда. А по поводу скорости и оптимизации и говорить не о чем.
По этому, имхо, ничего общего у меня с jQuery быть не может.

Хотя, если Вы гуру jQuery, я не против подискутировать.

dpp 24.09.2008 11:23

да...

поработав пол года js архитектором и оптимизатором проекта содержащего порядка 700 килобайт яваскрипта (и 70мб php кода) я начал несколько мягче относится к фреймворкам (у нас исторически prototype.js).

пальцы уже не корежит судорогой при написании `el.up('form').select('input[type=checkbox]')`. время разработки и понятность/объем кода того стоят. но всему есть предел - всякие `$A(...).each(...)` по-прежнему считаю абсолютно неприемлимыми (по поводу forEach обещаю статью).

dpp 26.02.2009 02:53

... и всего год мне потребовался чтобы, параллельно с текучкой, избавиться от каких-либо фреймворков :)
опять таки с целью оптимизации.

как-нибудь отпишусь - потрясающий опыт.

Riim 19.04.2009 12:16

Непонятно чем не угодил $A ?
В некоторых браузерах (IE например) работает немного быстрее, чем Array.prototype.slice.call(arguments, 0) , и чуть медленней на коротких коллекциях.
В других (например, Chrome) в несколько раз быстрей на коллекциях любой длинны.
К тому же неплохо сокращает объем кода.

dpp 21.04.2009 06:36

Не угодил Array.forEach - вызов функции на каждый элемент массива.

Riim 08.05.2009 08:09

В $.bind все же вместо $A всюду Array.prototype.slice .
Вероятно потому что нет реализации $A.
В общем, вам видней как свой код писать.

Riim 19.04.2009 12:42

И еще вопрос.
Видел множество реализаций метода bind для замыканий.
И во всех (как и в вашей) примерно следующее: fn.apply(context||null, arguments);
Т. е. context, в случае его отсутствия, заменяется на null.
Так вот мне непонятно: зачем на null? Почему не заменить на this? Ведь на практике это иногда жутко удобно. Может какая-то идеологическая (или как там это назвать) причина? Т. е. само название bind (не очень дружу с английским) предполагает какие-то особенности его реализации.

Написать комментарий

Вы представились как:   e-mail: email (изменить)

Ссылки запрещены.


Copyright © DPP, 2008-2009