Блог Конфуция
Такие разные removeClassName'ы 02.02.2009

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

Вот как у прототайпа 1.6:


removeClassName: function(element, className) {
  if (!(element = $(element))) return;
  element.className = element.className.replace(
    new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
  return element;
},
Тут мы видим два дополнительных вызова внешних функций: $(element) и .strip(). А еще строка собирается для каждого обращения. И еще регексп компилится каждый раз и при выполнении запоминает и заполняет $1 и $2. А зачем?

А вот так у джейквери:


remove: function( elem, classNames ) {
	if (elem.nodeType == 1)
		elem.className = classNames !== undefined ?
			jQuery.grep(elem.className.split(/\s+/), function(className){
				return !jQuery.className.has( classNames, className );
			}).join(" ") :
			"";
},
Этот код пожестче. Здесь и split с join'ом, и поиск className в jQuery, а в className'е потом поиск has. Ну, а про вызов замыкания на каждый имеющийся класс вообще умолчим. Задача укорачивания строки решена максимально помпезно.

В обеих библиотеках эти функции оборачиваются во что-то еще. Так, в коде джейквери прямым текстом написано «internal only, use removeClass("class")». Такой подход действительно полезен, когда к библиотеке пишут плагины и расширения. Хуков можно надобавлять, потрейсить, попрофайлить или еще что-нибудь побочное прикрепить.

На наших сайтах добавление и удаление классов происходит очень часто, а иногда очень-очень часто для целой кучи элементов (где именно? — как зарелизим — покажу; UPD 03.01.2009: показываю). Когда пишешь такой код, не хочется думать о том, что вызов removeClassName() может быть дорогим. Хочется простой и быстрый метод. Например, такой:


var rexCache = {}, R = RegExp
Element.prototype.removeClassName = function (cn)
{
	return this.className = this.className.replace
	(
		rexCache[cn] ||
		(rexCache[cn] = new R('(?:^| +)(?:' + cn + '(?:$| +))+', 'g')), ' '
	)
	.replace(/^\s+|\s+$/g, '')
}
Тут мы убрали все, что смогли, и ничего лишнего не добавили. Даже кеш, и тот закеширован в локальной переменной. Ах да: вы только, пожалуйста, не используйте эту функцию, не протестировав ее хорошенько на своих проектах. То, что она работает у нас, еще ничего не значит. Мы же не Резиг, и не мощная тусовка рельсовиков. Мы просто Иншейкер (а как же без ссылки на мой любимый коктейльный сайт).

Понимаю, в новых браузерах и вызовы методов быстрые, и встроенное кеширование регэкспов есть, и строки там молниеносные, и вообще у них уже джит на дворе. Так что париться о таких вещах как-то неудобно. Но меня не покидает чувство, что усложнять не нужно.

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

Теги:
  • клиент
  • DOM
  • javascript
  • lib
Очень жду ваших комментариев на почту или на гитхаб.