У вас по сути N систем из трех взаимосвязанных элементов.
Сами системы между собой никак не связаны, а вот элементы внутри системы между собой нужно как-то связать.
Самый простой вариант - использовать индексы (
интерактивный пример):
var buttons = document.querySelectorAll('.js-btn-item'),
popups = document.querySelectorAll('.js-products-popup'),
closers = document.querySelectorAll('.js-btn-close'),
index, button, popup, closer;
for (index = 0; index < buttons.length; index++) {
button = buttons[index];
popup = popups[index];
closer = closers[index];
button.addEventListener('click', buttonHandler(popup));
closer.addEventListener('click', closerHandler(popup))
}
function buttonHandler(popup) {
return function (event) {
event.preventDefault();
popup.classList.add('js-popup-show');
}
}
function closerHandler(popup) {
return function (event) {
event.preventDefault();
popup.classList.remove('js-popup-show');
}
}
Объяснение, зачем мы делаем такие сложные обработчики событий из двух вложенных функций, находится здесь. В современных браузерах можно сделать проще, но при этом внутри обработчика события this будет ссылаться уже не на элемент, а на что-то другое (скорее всего, window), но в данном случае это не имеет значения.
Для того, чтобы этот вариант работал, естественно, элементы каждого типа должны идти в строго одинаковом порядке.
Чтобы этого избежать, можно использовать родителя-обертку, внутри которого содержатся все 3 элемента системы (
пример). Такой подход, помимо всего прочего, за счет делегирования позволяет устанавливать N обработчиков, а не N*2, как в первом случае. Но код обработчика слегка усложняется - добавляется проверка целевого элемента.
Третий способ показал
Алексей Зуев - он более декларативный, как видите, javascript-кода там совсем немного - одна универсальная функция. Но этот вариант требует использования id вместо классов, это не всегда возможно.