Оптимизация JS кода табов

JavaScript начал учить недавно, хотелось бы узнать можно ли сократить запись табов JS чтоб выглядело более оптимизировано.

document.querySelectorAll('.conditions-control__item').forEach((item) => {
  item.addEventListener('click', function(e) {
    e.preventDefault();

    const id = e.target.getAttribute('href').replace('#', '')

    document.querySelectorAll('.conditions-control__item').forEach(
      (child) => child.classList.remove('conditions-control__item_active')
    );

    document.querySelectorAll('.conditions-content__item').forEach(
      (child) => child.classList.remove('conditions-content__item_active')
    );

    item.classList.add('conditions-control__item_active');

    document.getElementById(id).classList.add('conditions-content__item_active');
  });
});

document.querySelector('.conditions-control__item').click();
* { box-sizing: border-box; }
.conditions-control__item { display: inline-block; padding: 0.5em 2em; border: 1px solid #ccc; border-bottom-width: 0; }
.conditions-control__item_active { color: red; }
<div class="conditions-wrap">
  <div class="conditions-control">
    <a href="#tab-1" class="conditions-control__item">tab_1</a>
    <a href="#tab-2" class="conditions-control__item">tab_2</a>
  </div>
  <div class="conditions-content">
    <div id="tab-1" class="conditions-content__item">hello tab-1</div>
    <div id="tab-2" class="conditions-content__item">hello tab-2</div>
  </div>
</div>


Ответы (1 шт):

Автор решения: yar85

const tabs = document.querySelectorAll('.conditions-control__item'),
      tabsContent = document.querySelectorAll('.conditions-content__item');
const tabHandler = evt => {
  evt.preventDefault();
  const id = evt.target.getAttribute('href').replace('#', '');
  for (const tab of tabs)
    tab.classList.toggle('conditions-control__item_active', tab === evt.target);
  for (const content of tabsContent)
    content.classList.toggle('conditions-content__item_active', content.id === id);
};
for (const tab of tabs)
  tab.addEventListener('click', tabHandler);
tabs[0].click();
* { box-sizing: border-box; }
.conditions-control__item { display: inline-block; padding: 0.5em 2em; border: 1px solid #ccc; border-bottom-width: 0; }
.conditions-control__item_active { color: red; }
.conditions-content__item:not(.conditions-content__item_active) { display: none; }
<div class="conditions-wrap">
  <div class="conditions-control">
    <a href="#tab-1" class="conditions-control__item">tab_1</a>
    <a href="#tab-2" class="conditions-control__item">tab_2</a>
  </div>
  <div class="conditions-content">
    <div id="tab-1" class="conditions-content__item">hello tab-1</div>
    <div id="tab-2" class="conditions-content__item">hello tab-2</div>
  </div>
</div>

Изменения:

  • один общий обработчик клика по вкладке
  • переиспользование выборок из DOM (вкладки, элементы контента) выполняемых однократно
  • замена forEach на for..of (имхо, так код более читаемый)
  • использование метода classList.toggle вместо комбинации classList.remove + classList.add

Вместо цикла по всем элементам вкладок, можно просто снимать класс conditions-control__item_active у элемента текущей вкладки, а затем добавлять этот класс элементу той вкладки по которой кликнули.

То есть, заменив

for (const tab of tabs)
    tab.classList.toggle('conditions-control__item_active', tab === evt.target);

например на выборку querySelector'ом:

document.querySelector('.conditions-control__item_active').classList.remove('conditions-control__item_active');
evt.target.classList.add('conditions-control__item_active');

Альтернативами выборке одного элемента с "активным" классом может быть живая коллекция (исп. document.getElementsByClassName) или запоминание текущей вкладки в переменной.

При замене цикла на две операции, переменная tabs станет не нужна: клик по первой вкладке после инициализации можно выполнить выбрав ее вызовом document.querySelector('.conditions-control__item'), т.к. результатом вернется первый элемент вкладки в документе.

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

Если все же хочется больше оптимизаций именно в производительности, то вот (без циклов + делегирование обработки событий на захвате):

const CL_TAB = 'conditions-control__item',
      CL_CONTENT = 'conditions-content__item',
      CL_TAB_ACTIVE = CL_TAB + '_active',
      CL_CONTENT_ACTIVE = CL_CONTENT + '_active',
      tabsContent = [...document.querySelectorAll('.' + CL_CONTENT)];
let curTab = null,
    curContent = tabsContent[0];
document.querySelector('.conditions-control')
  .addEventListener('click', tabHandler, true);
document.querySelector('.' + CL_TAB).click();

function tabHandler(evt) {
  if (!evt.target.classList.contains(CL_TAB)) return;
  evt.preventDefault();
  if (evt.target === curTab) return;
  curTab?.classList.remove(CL_TAB_ACTIVE);
  (curTab = evt.target).classList.add(CL_TAB_ACTIVE);
  curContent.classList.remove(CL_CONTENT_ACTIVE);
  const id = evt.target.getAttribute('href').replace('#', '');
  (curContent = tabsContent.find(content => content.id === id))
    .classList.add(CL_CONTENT_ACTIVE);
}
* { box-sizing: border-box; }
.conditions-control__item { display: inline-block; padding: 0.5em 2em; border: 1px solid #ccc; border-bottom-width: 0; }
.conditions-control__item_active { color: red; }
.conditions-content__item:not(.conditions-content__item_active) { display: none; }
<div class="conditions-wrap">
  <div class="conditions-control">
    <a href="#tab-1" class="conditions-control__item">tab_1</a>
    <a href="#tab-2" class="conditions-control__item">tab_2</a>
  </div>
  <div class="conditions-content">
    <div id="tab-1" class="conditions-content__item">hello tab-1</div>
    <div id="tab-2" class="conditions-content__item">hello tab-2</div>
  </div>
</div>

→ Ссылка