Польза
Хардкор диагностики

Хардкор диагностики. Ориентирование на местности. Часть 1

8 января 2020 года
Битрикс, диагностика, jQuery, события, javascript, devtools.

Обнаружение поломок и их устранение — крайне сложное задание, именно потому специалистов, способных «разруливать» проблемы, высоко ценят. Когда нам нужно сориентироваться в ситуации, пригодится любой вспомогательный инструмент. Даже в работе со знакомым сайтом, а тем более, — на чужом проекте.

Собираем инструменты, которые могут облегчить жизнь хардкорщикам =)
Данная статья посвящена фронт-енду, перехвату подписывания на событие через jQuery.on.

jQuery.on

Нередко стоит задача обнаружить инициатора какого-либо события. Что происходит например при клике на кнопку поймать и понять сравнительно легко, но кто этот клик на кнопку повесил?

Инициализация событий в джиквери происходит через функцию .on

Как правило регистрацию события сопровождают:

  • this — объект (или список объектов), которые будут целью события;
  • само событие в виде строки («click», «load», ...);
  • колбэк-функция, которая должна быть вызвана при наступлении события.

Так как и регистрация и выброс события и выполнение колбэка происходит внутри jquery-библиотеки, затруднительно поймать того, кто событие на объект навесил. Самый простой способ получить нужную информацию — перехватить момент инициализации прослушки. Постараемся это сделать прямо в браузере, без вмешательства в js-код jquery-библиотеки. Джаваскрипт позволяет нам переопределять методы, этим и воспользуемся.

Переопределение метода

Предлагается сделать следующее:

  • ставим в девтулзах глобальный брекпоинт на событие document.onDOMContentLoaded, перезагружаем страницу;
  • на первом же срабатывании брекпоинта убеждаемся, что объект jQuery уже доступен и переопределяем ему метод on;
  • снимаем брекпоинт и отпускаем выполнение скрипта.

Код для выполнения в консоли:

// Сохраняем оригинальный метод
originalJqOn = jQuery.fn.on;

// Глобальный объект,
// в который будет собираться статистика
hardcoreFrontLog = {
  events: {},
  elements: []
};

// Переопределяем метод своим
jQuery.fn.extend({
  on: function (eventName, who, are, handler, here) {

    let info = {
      elements: [],
      events: []
    };

    let callback = null;

    if (who instanceof Function) {
      callback = who;
    } else if (are instanceof Function) {
      callback = are;
    } else if (handler instanceof Function) {
      callback = handler;
    } else if (here instanceof Function) {
      callback = here;
    }

    let err = new Error();
    info.trace = err.stack;
    info.callback = callback;
    info.selector = this.selector ? this.selector : null;
    info.arguments = arguments;
    info.this = this;

    if (this.length) {
      for (let i = 0; i < this.length; i++) {
        info.elements.push(this[i]);
      }
    } else {
      info.context = this.context;
    }

    if (eventName && (typeof eventName === "string" || eventName instanceof String)) {
      let eventNameList = eventName.split(' ');
      eventNameList.forEach(function (evt) {
        info.events.push(evt);
      });
    }

    // live! =)
    hardcoreFrontPrintResult(info, true);

    // Регистрируем в глобальном объекте
    if (this.length) {

      let logNode = {
        el: [],
        info: info
      };

      for (let i = 0; i < this.length; i++) {
        logNode.el.push(this[i]);
      }

      hardcoreFrontLog.elements.push(logNode);
    }

    if (eventName && (typeof eventName === "string" || eventName instanceof String)) {
      let eventNameList = eventName.split(' ');
      eventNameList.forEach(function (evt) {
        if (!hardcoreFrontLog.events[evt]) {
          hardcoreFrontLog.events[evt] = [];
        }
        hardcoreFrontLog.events[evt].push(info);
      });
    }

    // Пинаем оригинальный метод
    return originalJqOn.call(this, eventName, who, are, handler, here);

  }
});

// Метод для поиска среди собранной статистики
// информации по ДОМ-узлу
hardcoreFrontLookingByObj = function (el) {
  for (let e = 0; e < hardcoreFrontLog.elements.length; e++) {
    for (let ins = 0; ins < hardcoreFrontLog.elements[e].el.length; ins++) {
      if (hardcoreFrontLog.elements[e].el[ins] == el) {
        hardcoreFrontPrintResult(hardcoreFrontLog.elements[e].info);
      }
    }
  }
};

// Метод для поиска среди собранной статистики
// информации по названию события
hardcoreFrontLookingByEvent = function (event) {
  for (let e in hardcoreFrontLog.events) {
    if (!hardcoreFrontLog.events.hasOwnProperty(e)) {
      continue;
    }

    if (e !== event) {
      continue;
    }

    for (let ins = 0; ins < hardcoreFrontLog.events[e].length; ins++) {
      hardcoreFrontPrintResult(hardcoreFrontLog.events[e][ins]);
    }
  }
};

// Вывод в консоль
hardcoreFrontPrintResult = function (info, live) {
  let eventName = info.events;

  if (info.events.length) {
    info.events.forEach(function (evt) {
      console.log(
        '%c%s',
        'background: #222; color: #bada55; ' +
        'font-weight: bold; padding: 3px 4px;',
        evt
      );
    });
  }

  if (info.selector) {
    console.log(
      '%c%s',
      'background: #777; color: white; ' +
      'font-weight: normal; padding: 3px 4px;',
      info.selector
    );
  }

  if (info.elements.length) {

    if (info.elements.length > 2) {
      console.groupCollapsed('elements');
    }

    for (let i = 0; i < info.elements.length; i++) {
      console.log(info.elements[i]);
    }

    if (info.elements.length > 2) {
      console.groupEnd();
    }
  } else {
    console.log(info.elements.context);
  }

  console.groupCollapsed('trace');
  if (live) {
    console.trace();
  } else {
    console.log(info.trace);
  }
  console.groupEnd();

  console.groupCollapsed('info');
  console.log(info);
  console.groupEnd();
};
            

Статистика

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

Битрикс. Статистика по каждому навешанному событию

Также вся информация будет накапливаться в глобальный объект hardcoreFrontLog, отсортированная отдельно по событиям, отдельно по ДОМ-узлам. К сожалению, полноценный трейсинг в переменную не запомнить, но часть цепочки сохраняется, через объект Error. Это хотя бы частично позволяет сориентироваться в том, откуда пришёл сигнал.

Собранную на текущий момент статистику по событиям получаем через функцию
hardcoreFrontLookingByEvent(название_события).

Статистику по конкретному DOM-объекты получаем через
hardcoreFrontLookingByObj(указатель_на_объект).

Обсуждение статьи

Написать