Разбор 03

Перестал работать JavaScript на сайте

Кнопка не нажималась, слайдер прокручивался в одну сторону и застревал, а модальное окно вообще не открывалось. В консоли — россыпь ошибок.

JavaScript jQuery Slick Slider DOM консоль noConflict
Клиент
Корпоративный сайт на WordPress (устаревшая тема)
Стек
jQuery 1.12, slick slider 1.8, кастомный JS, wp_enqueue_script
Срок
1 день
Итог
Консоль чистая, весь интерактив работает, jQuery подключается один раз

Суть проблемы

Клиент поставил новый плагин формы обратной связи, и «всё сломалось»: три независимых интерактив-элемента перестали реагировать. Поверхностно симптомы выглядят как три разных бага, но корневая причина одна — нарушенный порядок загрузки скриптов и дублирование jQuery.

Состояние сайтаДо

6 ошибок в Console, 3 нерабочих интерактив-элемента, jQuery подключён дважды (header + footer), slick.min.js грузится раньше jQuery, кастомный JS в <head> без ожидания DOM.

Состояние сайтаПосле

0 ошибок в Console, все 3 элемента работают, jQuery подключён один раз, зависимости выстроены через ['jquery'], кастомный JS обёрнут в jQuery(function($){ ... }) с делегированием событий.

Как диагностировал

Сначала собрал все ошибки из консоли — они указали на два разных класса проблем: потерю плагина slick и обращение к несуществующему $. Дальше пошёл по Network и Sources, чтобы понять, почему $.fn оказалась пустой.

  1. Собрал ошибки в Console$(...).slick is not a function — плагин slick недоступен на момент вызова. $ is not a function — глобальный $ перезаписан или отсутствует. Cannot read properties of null — селектор вернул пустой набор, обращение к .style/.classList падает.
  2. Проверил вкладку Network → JSУвидел, что jquery.js грузится дважды: версия 1.12 из темы в <head> и версия 3.6 из нового плагина в <footer>. Вторая загрузка перезаписывает window.jQuery и window.$, и все плагины, навешенные на первую jQuery (включая slick), теряются — $.fn.slick становится undefined.
  3. Разобрался с кнопкой «Заказать звонок»Кастомный скрипт лежал в <head> без defer и без DOMContentLoaded. На момент выполнения $('.callback-btn') элемента ещё не было в DOM — обработчик навешивался на пустой набор. Плюс после второй загрузки jQuery $ уже указывал на новую версию.
  4. Разобрался с модалкойВ коде был селектор $(.modal) — без кавычек. JavaScript трактовал .modal как обращение к глобальной переменной (числовой литерал .modal → ReferenceError в строгом режиме, в нестроком — undefined). На деле $ уже не был функцией, поэтому падало ещё раньше.
  5. Проверил порядок загрузки slickВ functions.php темы slick регистрировался через wp_enqueue_script('slick', ...) без указания зависимости ['jquery']. Из-за этого slick.min.js мог оказаться в выдаче раньше jquery.js — плагин подключался к несуществующему $ и молча не регистрировался.
  6. Сверил с моментом поломкиКлиент подтвердил: всё сломалось ровно после активации нового плагина формы. Плагин через wp_enqueue_script('jquery', ...) подтягивал свою jQuery 3.6 в footer — это и была вторая загрузка, перетёршая $.fn.

Что исправил

Чинил снизу вверх: сначала выстроил правильный порядок и убрал дубликат jQuery, затем починил регистрацию slick с зависимостью, потом переписал кастомный JS под безопасную обёртку и делегирование событий.

1. Регистрация скриптов в functions.php

Тема подключала jQuery вручную тегом <script> в header, а slick регистрировался без зависимости. Новый плагин подтягивал свою jQuery в footer — итого два экземпляра.

functions.phpбыло
// jQuery подключён вручную в header.php:
// <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>

function theme_enqueue_assets() {
    // slick без зависимости от jquery — может загрузиться раньше
    wp_enqueue_script(
        'slick',
        get_template_directory_uri() . '/js/slick.min.js',
        array(),          // <-- нет ['jquery']
        '1.8.1',
        true
    );

    wp_enqueue_script(
        'theme-custom',
        get_template_directory_uri() . '/js/custom.js',
        array('slick'),  // зависит от slick, но не от jquery
        '1.0.0',
        false             // грузится в <head> -- ДО DOM
    );
}
add_action('wp_enqueue_scripts', 'theme_enqueue_assets');
functions.phpстало
// Убрали ручной <script src jquery> из header.php.
// WordPress сам подключит jQuery из ядра один раз.

function theme_enqueue_assets() {
    // jQuery из ядра WP — одна общая версия, без дублей
    wp_enqueue_script('jquery');

    wp_enqueue_script(
        'slick',
        get_template_directory_uri() . '/js/slick.min.js',
        array('jquery'),  // slick ждёт jquery
        '1.8.1',
        true
    );

    wp_enqueue_script(
        'theme-custom',
        get_template_directory_uri() . '/js/custom.js',
        array('jquery', 'slick'),  // ждёт и jquery, и slick
        '1.0.1',
        true            // в footer -- после DOM
    );
}
add_action('wp_enqueue_scripts', 'theme_enqueue_assets');

// Гарантированно снимаем дубль jQuery, который тянет плагин формы
function theme_dequeue_plugin_jquery() {
    // Плагин регистрировал свою jquery в footer -- снимаем
    wp_dequeue_script('plugin-form-jquery');
    wp_deregister_script('plugin-form-jquery');
}
add_action('wp_enqueue_scripts', 'theme_dequeue_plugin_jquery', 100);
Две jQuery = потерянные плагины. Когда jQuery грузится второй раз, она перезаписывает window.jQuery и window.$. Все плагины (.slick, .fancybox, .select2 …), навешенные на первый экземпляр через $.fn, исчезают — вызовы падают с ... is not a function. Правильный путь — одна jQuery из ядра WordPress, все остальные скрипты через ['jquery'].

2. Кастомный JS: обёртка, делегирование и guard

Скрипт в <head> выполнялся до построения DOM, использовал глобальный $ (который после второй загрузки jQuery уже не та функция) и обращался к элементам напрямую. Переписал под безопасный паттерн.

js/custom.jsбыло
// Скрипт в <head>, без ожидания DOM:

// Элемента ещё нет в DOM -- обработчик навешивается в никуда
$('.callback-btn').on('click', function() {
    $(.modal).addClass('open');  // .modal без кавычек -- ReferenceError
});

// $ после второй загрузки jQuery уже не содержит .slick
$('.hero-slider').slick({
    arrows: true,
    autoplay: true,
    autoplaySpeed: 4000
});

// Закрытие модалки -- тоже на прямой селектор
$('.modal-close').on('click', function() {
    $('.modal').removeClass('open');
});
js/custom.jsстало
// Локальный $ внутри замыкания -- не зависит от того,
// что лежит в window.$ на момент выполнения.
// Срабатывает по DOMContentLoaded (jQuery готовит DOM сам).
jQuery(function($) {

    // Делегирование: работает даже если кнопка отрендерится
    // плагином позже или перерисуется.
    $(document).on('click', '.callback-btn', function(e) {
        e.preventDefault();
        var $modal = $('.modal');
        if ($modal.length === 0) return;  // guard
        $modal.addClass('open');
    });

    // Инициализация slick только если элемент есть
    var $slider = $('.hero-slider');
    if ($slider.length && $.fn.slick) {
        $slider.slick({
            arrows: true,
            autoplay: true,
            autoplaySpeed: 4000
        });
    }

    // Закрытие -- тоже через делегирование
    $(document).on('click', '.modal-close', function() {
        $('.modal').removeClass('open');
    });
});

Что именно починило каждое

Слайдер: зависимость ['jquery'] гарантирует, что slick грузится после jQuery, а $.fn.slick проверка защищает от тихого падения. Кнопка и модалка: обёртка jQuery(function($){ ... }) ждёт DOM и даёт локальный $; делегирование через $(document).on(...) переживает перерисовку; guard if ($modal.length === 0) return убирает Cannot read properties of null.

Результат

После правок порядка загрузки и переписывания кастомного JS консоль стала чистой, а весь интерактив заработал стабильно. Повторное подключение jQuery исчезло, объём передаваемого JS в шапке уменьшился.

6 → 0
0
ошибок в Console
3 → 0
0
нерабочих интерактив-элементов
2 → 1
1
подключение jQuery
+90 КБ → убрано
−90 КБ
дублирующий jquery.js в footer

Слайдер, кнопка «Заказать звонок» и модальное окно формы работают стабильно, перерисовка DOM плагином перестала ломить обработчики. JS-зависимости выстроены через wp_enqueue_script с корректными массивами ['jquery'], так что новый плагин больше не может «перетереть» jQuery незаметно.

Итог. Корневая причина — не три отдельных бага, а двойная загрузка jQuery и нарушенный порядок скриптов. Устранив дубль и обернув кастомный JS в безопасный паттерн с делегированием, закрыл сразу все симптомы. Консоль чистая, сайт интерактивен.
Есть похожая проблема?

Сначала найдём причину. Потом решим, что действительно нужно исправить.

Опишите симптом и что из-за него перестало работать. Я посмотрю, где искать причину и насколько задача похожа на точечный фикс.

Описать проблему ↗