В приведённых функциях происходит двойное обращение к свойству объекта className, которое лучше было бы «закэшировать», получив один один раз и сохранив в строковой переменной.

Функция для работы с className в javascript

Изменение атрибута class у HTML-элементов является основой, базой для управления содержимым страницы с помощью DHTML. Любое действие по оформлению проще всего производить, именно изменяя динамически свойство className: от простого добавления правила {cursor:pointer;} при подготовке таблиц к сортировке до создания сложных электронных документов (пример: http://infodisk.info/konstitut.htm).

Работа с className – одна из важнейших и наиболее частых задач DHTML, поэтому функции Javascript, выполняющие эту работу, должны быть написаны очень тщательно.

Что делать?

Какие конкретно действия можно выполнять с атрибутом class у HTML-элемента? Значение этого атрибута может состоять из нескольких слов (разделённых пробелами), каждое из которых является отдельным селектором класса для набора правил CSS: <p class='flole border'> (а в CSS может быть написано .flole {float:left;} .border {border:1px solid gray;}). Значит, можно (1) добавлять к атрибуту class новые слова-селекторы; (2) удалять существующие селекторы; а иногда (например, при чередующейся раскраске строк в таблице) требуется и заменять (3) один селектор на другой.

То есть достаточно традиционный набор действий: add, remove, replace. Чаще всего Javascript-библиотеки обходятся первыми двумя, для которых делают отдельные функции вида:

Ну, а чтобы лишний раз не менять className (не заставлять браузер перерисовывать страницу и тормозить всю работу), в набор добавляют ещё функцию проверки существования:

А потом используют результат этой функции перед добавлением или удалением «слов» из className:

Почему надо делать не так (можно ли лучше)?

Приведённый набор функций, в принципе, можно считать стандартным – все используют более-менее похожие алгоритмы. Они хороши для «средней» нагрузки, но для пересчёта большой таблицы (или просто длинного списка элементов) скорость работы лучше бы повысить.

Оптимизировать алгоритмы add-remove можно в двух местах. А если возникает ещё и третья задача (replace – заменить один селектор на другой), результаты оптимизации будут, как миним, умножены на два.

  1. Проверку на вхождение слова в строку в функции hasClass быстрее можно выполнить не с помощью регулярного выражения, а простым строковым методом indexOf(). С учётом того что селекторы могут отделяться друг от друга только пробелами, основное действие оптимизированной функции hasClass будет выглядеть так: return (" " + el.className + " ").indexOf(" " + cls + " ") !== -1
  2. Во всех приведённых функциях происходит двойное обращение к свойству объекта className, которое лучше бы «закэшировать», получив один один раз и сохранив в строковой переменной, а потом передавать функциям в качестве параметра эту строку, а не HTML-элемент. В этом случае, конечно, функции изменения должны будут возвращать новую строку, которую и надо будет присваивать элементу HTML в качестве атрибута class. Или НЕ присваивать, если строка не изменилась.

Переделанные функции (и вся система управления className) могли бы выглядеть примерно так:

Получилось «правильно», но не очень красиво. Обычно так всегда и бывает – более правильный код более длинный. Да. А если класснэйм надо добавлять раз 20, код станет совсем длинным, и уже нерациональным. Вполне логично делать одни и те же действия по извлечению строки всё-таки внутри часто повторяющейся функции, как-нибудь так:

Правда, в последнем примере (и в предыдущем, если нет элемента с ид "first") вывалится ошибка, даже несмотря на заботливо добавленный "el && " перед получением el.className. А ещё хуже, если функцию hasClass вызвать с двумя «плохими» параметрами (или без параметров) – она вернёт true. Лекарство во всех этих случаях двойное:

1) проверять в функции hasClass наличие обоих параметров, и если хотя бы один не приводится к true (undefined, или просто не содержит букв), возвращать false (что означает: «нет такого селектора в данном классе»);

2) в «рабочих» функциях (меняющих класснейм) проверять наличие свойства className у первого параметра (который должен быть элементом); причём, рекомендуемая специалистами проверка вида ("className" in el) здесь не подходит, так как мы пытаемся обеспечить получение какого попало параметра (например, не объекта); поэтому проверять будем на undefined:

Окончательная оптимизация

Рабочих функции у нас три, и во всех теперь повторяется одна и та же цепочка предварительных действий. И она ещё удлинится: мы всё-таки хотим обрабатывать с помощью данных функций и объекты (элементы), и строки («я не жадный, я запасливый»). Что-то регулярно повторяющееся и длинное надо выносить в отдельную функцию (зачем ещё нужны функции?).

В нашем случае функции так малы, что проще не выносить из них что-то, а объединить в одну. Так мы и сделали – одну функцию с тремя параметрами: 1) элемент (объект), 2) селектор (строка или массив из двух строк, которые надо менять одна на другую), 3) указатель на выбо действия (число): 1 – добавить селектор, 2 – удалить, 3 – заменить.

Но чего-то в такой конструкции опять нам не хватало – может быть, разные типы второго параметра не нравились, может переключатель действий с помощью switch... И тогда мы придумали сделать переключатель действий не на основе значения отдельного параметра, а на основе наличия параметров – всё, наконец, получилось логично и стройно. Вот оно, это всеобщее уравнение селекторов CSS:

Строк, может, получилось и больше, чем было бы в отдельных функциях, но зато красивее. Мудрый читатель удивится, почему сюда до кучи не добавили функцию проверки класса (hasClass). – Потому что проверять наличие селектора в атрибуте «класс» бывает нужно не только для изменения этого атрибута, но и просто для поиска (отбора) элементов. Эта функция, кстати, позволяет корректно использовать для выборки из HTML-коллекций конструкции for (var i in list), например, так:

Собственно, проверка if (!elCls || !cls) return false в функции hasClass именно для этой цели и была изначально придумана.

D.M., admin

Комментарии