Для выборки элементов из DOM я практически всегда использую выборку по ID. Поэтому, для увеличения продуктивности, считаю оправданным дополнительный вызов функции обертки. Кроме того, имя этой функции используется в качестве namespace для базовых функций.
/**
* getElementById
* @param {String} id
* @return {HTMLElement}
*/
function $(id)
{
return document.getElementById(id);
}
Изначально эта фукциия была умнее и проверяла типы и количество передаваемых параметров (а-ля prototype.$). Однако практика (и статистика) показала, что эти возможности я практически никогда не использовал, и они были выкинуты.
Для перегруженных приложений иногда использую кэширующую версию.
/**
* 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. Поэтому я вынес проверку этого (и только этого) браузера в самое начало списка необходимых функций. Тем более, что она простая и 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){}
Для наследования объектов 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;
};
Одним из наиболее привлекательных подходов в 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];
};
Простейший обработчик шаблонов на 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:'');
}
);
Основопологающей особенностью 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 initially hidden block
* @param {String} id
*/
function toggle(id)
{
var o=($(id)||{}).style;
if(o) o.display ? o.display='' : o.display='block';
}
Функция, оставшаяся от моего модемного прошлого, - явная загрузка картинок. Картинка может быть не видна после загрузки страницы, и браузер ее не загружает до тех пор, пока она не станет видимой. Но если мы уже отключились от интернета, картинку взять негде. Чаще всего такая ситуация возникает для стилей с псевдоклассом :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];
}
}
Комментарии приветствуются.
Почему не jQuery? Тем более очень похоже.
Разве?
Если честно - я не понимаю идеи jQuery. Основной упор у нее на сложные выборки элементов и цепочки вызовов функций. У меня подобных задач не возникает практически никогда. А по поводу скорости и оптимизации и говорить не о чем.
По этому, имхо, ничего общего у меня с jQuery быть не может.
Хотя, если Вы гуру jQuery, я не против подискутировать.
да...
поработав пол года js архитектором и оптимизатором проекта содержащего порядка 700 килобайт яваскрипта (и 70мб php кода) я начал несколько мягче относится к фреймворкам (у нас исторически prototype.js).
пальцы уже не корежит судорогой при написании `el.up('form').select('input[type=checkbox]')`. время разработки и понятность/объем кода того стоят. но всему есть предел - всякие `$A(...).each(...)` по-прежнему считаю абсолютно неприемлимыми (по поводу forEach обещаю статью).
... и всего год мне потребовался чтобы, параллельно с текучкой, избавиться от каких-либо фреймворков :)
опять таки с целью оптимизации.
как-нибудь отпишусь - потрясающий опыт.
Непонятно чем не угодил $A ?
В некоторых браузерах (IE например) работает немного быстрее, чем Array.prototype.slice.call(arguments, 0) , и чуть медленней на коротких коллекциях.
В других (например, Chrome) в несколько раз быстрей на коллекциях любой длинны.
К тому же неплохо сокращает объем кода.
Не угодил Array.forEach - вызов функции на каждый элемент массива.
В $.bind все же вместо $A всюду Array.prototype.slice .
Вероятно потому что нет реализации $A.
В общем, вам видней как свой код писать.
И еще вопрос.
Видел множество реализаций метода bind для замыканий.
И во всех (как и в вашей) примерно следующее: fn.apply(context||null, arguments);
Т. е. context, в случае его отсутствия, заменяется на null.
Так вот мне непонятно: зачем на null? Почему не заменить на this? Ведь на практике это иногда жутко удобно. Может какая-то идеологическая (или как там это назвать) причина? Т. е. само название bind (не очень дружу с английским) предполагает какие-то особенности его реализации.