Хардкор диагностики. Ориентирование на местности. Часть 1
Обнаружение поломок и их устранение — крайне сложное задание, именно потому специалистов, способных «разруливать» проблемы, высоко ценят. Когда нам нужно сориентироваться в ситуации, пригодится любой вспомогательный инструмент. Даже в работе со знакомым сайтом, а тем более, — на чужом проекте.
Собираем инструменты, которые могут облегчить жизнь хардкорщикам =)
Данная статья посвящена фронт-енду, перехвату подписывания на событие через 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(указатель_на_объект)
.
Обсуждение статьи