Pull to refresh

Эмуляция события вызова функции

Reading time 2 min
Views 5.3K
Около полугода назад мне задали примерно такой вопрос: “Я использую крупный фреймворк, запускающий некую функцию по действию пользователя. Хочу, не меняя кода этой функции, выполнить по событию вызова этой функции свой код.”. Практика далеко не лучшая, события вызова функции не существует, я просто покрутил пальцем у виска и сказал, что это жуткий говнокод, так делать не стоит, да и это, просто-напросто, — невозможно.

Пару дней назад, направляясь домой относительно тёплым зимним вечером, не типичным для Одессы, у меня возникла нетипичная мысль: “А что если попробовать сделать то, что спрашивал Богдан полгода назад?”. Придя домой, я включил компьютер, и, в течении пары минут, сделал то, что задумал. В первую очередь, меня интересовало то, как будут себя вести встроенные методы, если их переопределить, и можно ли после этого как-нибудь вызвать прежнее их состояние, бывшее до переопределения. Я знал, что, если объект переопределяют, то ссылки на него не уничтожатся, сохраняя прежний вид. Что касается встроенных функций, имелись сомнения. Оказывается, можно.

Скорее всего, мне даже никогда не придется воспользоваться таким инструментом, но, чисто гипотетически, возможно, у кого-то возникнет задача проследить вызов той или иной функции, получив отчет о каждом вызове, состоящий из:
  1. Результата выполнения
  2. Переданных аргументов
  3. Контекста вызова (что есть this при вызове)
  4. Количества вызовов функции после создания обработчика

(Этот список полностью соответствует аргументам, передающимся в обработчик)

    addCallListener = function(func, callback){
        var callNumber = 0;
        return function(){
            var args = [].slice.call(arguments);
            var result;
            try {
                result = func.apply(this, arguments);
                callNumber++;
            } catch (e) {
                callback(e, args, this, callNumber);
                throw e;
            }
            callback(result, args, this, callNumber);
            return result;                 
        }
    }


Очень просто и кратко, правда?

Затем, переопределяем какую-нибудь функцию следующим образом:


someFunct = addCallListener(someFunct, function(result, args, self, callNumber){
     //Do Something
});
// или
Constructor.prototype.method = addCallListener(Constructor.prototype.method, function(result, args, self, callNumber){
     //Do Something
});


Очевидно, она должна быть «видна» в той части кода, где это происходит.

Несколько примеров
Для запуска примеров необходимо наличие открытой консоли (в хроме вызывается по Ctrl+Shift+i).

Отслеживание анимаций:
jsfiddle.net/finom/DnRD8
Событие выполнения метода push в массиве:
jsfiddle.net/finom/bbHhH/1
Событие выполнения какой-то другой функции:
jsfiddle.net/finom/ah5My/3

The End.

Спасибо товарищам с форума javascript.ru за критику и дополнения.

UPD
Немного расширил функцию: теперь вместо одного колбека вторым аргументом передается объект
  1. before (вызывается перед запуском функции)
  2. success (вызывается, если функция отработала успешно)
  3. error (если возникла ошибка)
  4. after (вызывается в любом случае, не зависимо от успешности выполнения)

Каждому обработчику передается объект
  1. args
  2. self (контекст)
  3. name (имя функции)
  4. status («error» или «success»)
  5. successNumber (количество успешных вызовов)
  6. errorNumber (количество вызовов с ошибкой)
  7. result (результат, если есть)
  8. error (ошибка, если есть)

Кроме этого, функция addCallListener теперь является частью объекта конструктора Function, во избежание попадания в window или process.

Форкнуть и потестить можно здесь: jsfiddle.net/finom/SGhzd/5
Tags:
Hubs:
+16
Comments 24
Comments Comments 24

Articles