Слушаем вызовы функций в Javascript

    Многие знают о механизме Event-Dispatcher-Listener'ов, реализованному во многих языках программирования. Я же создам подобный механизм не для Event'ов, а для любого метода объекта JavaScript — Object.
    Я не претендую на оригинальность, нет. Основная цель статьи — рассмотреть интересные механизмы прототипирования в JavaScript, создание декораторов и, собственно, постараться хоть немного раскрыть мощь и гибкость это чудесного языка, который так часто обижают и недооценивают.

    UPD1: краткое содержание:
    1. Создание декоратора на JavaScript
    2. Создание механизма Function call listener с помощью декораторов

    UPD2: от 09.06.2009
    В конце статьи я добавил раздел Update: Decorator Mark II. В нём — исправления и улучшения (я не перекраивал всю статью из-за этого)

    Итак, первый (и, надеюсь, не последний) рецепт в моей поваренной книге JavaScript.

    Блюдо


    Function Call Listener, фаршированный (многофункциональный), запечённый в собственном соку (без использования каких-то библиотек, на чистом JavaScript).

    Ингредиенты


    1. Понимание прототипирования в JavaScript
    2. Понимание анонимных функций JavaScript
    3. Приблизительное знание класса Function

    Не пугайтесь, я опишу ингредиенты поподробней.

    Рецепт


    Нырнём на пару метров вглубь


    Итак, приступим. Вначале позаимствуем небольшого помощника у John Resig'а (из его книги):
    Function.prototype.method = function(methodName, f) {
      return this.prototype[methodName] = f;
    }


    * This source code was highlighted with Source Code Highlighter.


    Что делает этот метод? Дословно: он позволяет легко добавлять новый метод в прототип текущей функции.
    Как вы знаете, функция в JavaScript является также классом и конструктором класса (не бейте, сдаюсь — нет в JavaScript никаких классов, в привычном понимании).

    Верно и следующее: конструктор любого класса — это функция (или по-научному — экземпляр класса Function).
    Исходя из принципов прототипирования — после добавления нового метода method(...) в прототип класса Function, у всех экземпляров класса Function появился новый метод method(...) (но помни: в JavaScript нет никаких методов, Нео).

    Любой классObject, Array, Number, YourClassName — является экземпляром класса Function, т.е. просто функцией. А значит у нас появились: Object.method(...), Array.method(...), YourClassNam.method(...)

    Если считать функцию — привычным классом, то мы по сути добавили статический метод всем-всем-всем классам (JavaScript реально вставляет :-) ).

    Строим декоратор


    Ладно, хватит о гениальном. Перейдём теперь к моему коду:
    Function.method("decorate", function(f) {
      var oldMe = this;
      var newMe = f;
      newMe.old = oldMe;
      return newMe;
    })


    * This source code was highlighted with Source Code Highlighter.

    Вуаля! Как я говорил, класс Function сам по себе является функцией, или экземпляром класса Function (а-а-а :-))
    А значит, как только мы добавили:
    Function.prototype.method = function(...) {}

    * This source code was highlighted with Source Code Highlighter.

    У нас тут же появился Function.method(...) (уже не в прототипе, а в экземпляре класса Function)

    После выполнения кода выше у нас появится новый метод Function.prototype.decorate(...). И опять же — метод появился в прототипе Function, а значит и во всех-всех-всех классах. Но вот тут как раз мне это не принципиально, а важно лишь присутствие метода decorate(...) у всех функций.

    Что делает метод decorate(...)?
    // сохраняем старую функцию
    var oldMe = this;

    // возвращаем переданную в метод новую функцию, которая "за пазухой" хранит старую.
    var newMe = f;
    newMe.old = oldMe;
    return newMe;


    * This source code was highlighted with Source Code Highlighter.

    Конечно этот код можно сильно сократить, но так — более наглядно.

    Но это не всё, самое интересное — впереди. Вы думаете я зря назвал мой метод — decorate? Нет, не зря! Это и есть знакомый многим декоратор.

    Пример декорирования


    Создадим простенький класс:
    // мега-класс, содержащий число
    function MyCoolNumber(initNumber) {
      this.value = initNumber;
    }


    * This source code was highlighted with Source Code Highlighter.

    Но число ли? Нет, конечно. Я могу передать туда всё, что угодно.
    new MyCoolNumber('на ура') // пройдёт "на ура"

    * This source code was highlighted with Source Code Highlighter.

    Но что же делать? Мне категорически не хочется менять конструктор. Выход есть: напишем декоратор для ограничения передаваемых параметров и применим его к нашему классу.
    function strictArgs() { // здесь будет переменное число аргументов, поэтому я их не именую
      var types = arguments; // передаём типы
      return function() {
        var params = arguments;
        if (params.length != types.length)
          throw "Ошибка! Ожидалось " + types.length + " аргумент(ов), а пришло " + params.length;
        for (var i=0, l=params.length; i < l; i++) {
          if (!(params[i] instanceof types[i]) && !(params[i].constructor == types[i]))
            throw "Ошибка! Аргумент #" + (i+1) + " должен быть " + types[i].name;
        }
        arguments.callee.old.apply(this, arguments); // собственно, вызов "старой" функции
      }
    }


    * This source code was highlighted with Source Code Highlighter.

    Вернёмся к нашему подопытному:
    function MyCoolNumber(initNumber) {
      this.value = initNumber;
    }
    MyCoolNumber = MyCoolNumber.decorate(strictArgs(Number))

    new MyCoolNumber(); // Ошибка! Ожидалось 1 аргумент(ов), а пришло 0
    new MyCoolNumber(1, 2, 3); // Ошибка! Ожидалось 1 аргумент(ов), а пришло 3
    new MyCoolNumber("строка"); // Ошибка! Аргумент #1 должен быть Number
    var x = new MyCoolNumber(6); // OK!
    alert(x.value) // 6, что и следовало ожидать, значит декоратор отработал нормально.


    * This source code was highlighted with Source Code Highlighter.

    Рассмотрим всё это безобразие повнимательнее. Начнём с момента применения декоратора.
    1. вызывается ф-ция strictArgs с одним аргументом — Number
    2. var types = arguments; // мы сохраняем аргументы вызова функции strictArgs(...) в переменную types, которая по сути содержит сейчас [Number] - массив из 1 эл-та: класса Number. Конечно объект типа Arguments - это не массив, но сейчас не об этом, и будем считать его массивом
    3. strictArgs(...) возвращает новую ф-цию, внутри которой:
      1. var params = arguments; // мы точно так же сохраняем аргументы вызова, но уже аргументы, переданные в наш будущий обновлённый MyCoolNumber;
      2. начинаем сравнивать эти 2 массива на совпадение размерностей и типов

    4. мы декорируем ф-цию MyCoolNumber той, которая вернулась из strictArgs(...)
      Заметим: связь оригинальной ф-ции с декоратором осуществляется через arguments.callee.old.apply(this, arguments):
      • arguments — стандартный объект для описания аргументов вызываемой функции
      • arguments.callee — сама функция-декоратор
      • arguments.callee.old — помните, что такое — old? Когда мы передаём функцию-декоратор в метод decorate(...), он добавляет этой ф-ции атрибут old, ссылающийся на «старую» ф-цию
      • arguments.callee.old.apply(...) — стандартный метод класса Function. Не буду о нём, скажу лишь, что он вызывает ф-цию с заданным scope и arguments
      • arguments.callee.old.apply(this, arguments) — собственно, подтверждение вышесказанного


    Вот так, вроде бы на основных моментах я заострил внимание.
    Ах да, забыл! Как «скинуть» с функции декоратор и вернуть старую? Нет ничего проще:
    Function.method("recover", function() {
      return this.old || this;
    })


    * This source code was highlighted with Source Code Highlighter.

    Теперь продолжим!

    Смотрим на объект, слушаем методы


    Мы плавно подходим к завершению моего рецепта. Еще чуть-чуть специй, и можно в топку… тьфу, в духовку! :)
    Object.method('before', function(methodName, f){
      var method = listenerInit.call(this, methodName);
      if (method)
        method.listenersBefore.push(f);
    })

    Object.method('after', function(methodName, f){
      var method = listenerInit.call(this, methodName);
      if (method)
        method.listenersAfter.push(f);
    })


    * This source code was highlighted with Source Code Highlighter.

    Как можно догадаться, всё самое важное происходит внутри некой ф-ции listenerInit(...), но о ней — позже. Пока-что просто поверим, что она делает все необходимые приготовления.
    Как добавить listener — понятно. Теперь нужна возможность его «убрать»:
    Object.method('removeBefore', function(methodName, f){
      var method = listenerInit.call(this, methodName);
      if (method) {
        var _nl = [];
        while (method.listenersBefore.length) {
          var _f = method.listenersBefore.shift();
          if (_f != f)
            _nl.push(_f);
        }
        method.listenersBefore = _nl;
      }
    })

    Object.method('removeAfter', function(methodName, f){
      var method = listenerInit.call(this, methodName);
      if (method) {
        var _nl = [];
        while (method.listenersAfter.length) {
          var _f = method.listenersAfter.shift();
          if (_f != f)
            _nl.push(_f);
        }
        method.listenersAfter = _nl;
      }
    })


    * This source code was highlighted with Source Code Highlighter.

    Может этот способ и не оптимальный, я просто взял первый из головы.
    Наконец-то, венец этого рецепта, связь декоратора и event-listener схемы — та самая ф-ция listenerInit:
    function listenerInit(methodName) {
      
      var method = this[methodName];
      if (typeof method != "function")
        return false;
      
      // продекорировано, или ещё нет?
      if (!method.listenable) {
        this[methodName] = method.decorate(function(){
          var decorator = arguments.callee;
          decorator.listenable = true;
          
          var list = decorator.listenersBefore;
          for (var i = 0, l = list.length; i < l; i++) {
            if (typeof list[i] == "function" && list[i].apply(this, arguments) === false)
              return;
          }
          
          var ret = decorator.old.apply(this, arguments);
          list = decorator.listenersAfter;
          for (var i = 0, l = list.length; i < l; i++)
            list[i].apply(this, arguments);
            
          return ret;
        });
        method = this[methodName];
      }
      
      method.listenersBefore = method.listenersBefore instanceof Array ? method.listenersBefore : [];
      method.listenersAfter = method.listenersAfter instanceof Array ? method.listenersAfter : [];
      
      return method;
    }


    * This source code was highlighted with Source Code Highlighter.

    Эту функцию можно условно разделить на блоки.

    Блок 1: проверка — не обманывают ли нас, есть ли в этом объекте такой метод?
    var method = this[methodName];
    if (typeof method != "function")
      return false;


    * This source code was highlighted with Source Code Highlighter.

    А в конце видим: return method, т.е. listenerInit(...) возвращает либо false, либо уже «украшенный» метод.

    Блок 2: создание соответствующего декоратора, если таковой ещё не определён.
    Что же в этом декораторе?
    1. Запускаем все listener'ы из массива listenersBefore. Если хоть 1 из них возвращает Boolean false — прекращаем выполнение
    2. Вызов базового метода
    3. Запускаем все listener'ы из массива listenersAfter
    4. Декоратор возвращает то значение, которое вернул базовый метод

    Блок 3: инициализация массивов method.listenersBefore и method.listenersAfter.

    Плюшки


    Модификация 1: спрячем ф-цию listenerInit с глаз долой. Для этого используем JavaScript-замыкание:
    (function(){
     // listenerInit(...) и все-все-все
     // .....
    })()


    * This source code was highlighted with Source Code Highlighter.

    Модификация 2: загрязнять стандартные классы вроде Object — очень плохо, поэтому можно модифицировать ваш конкретный класс:
    YourClass.method('before', function(methodName, f){
      var method = listenerInit.call(this, methodName);
      if (method)
        method.listenersBefore.push(f);
    })


    * This source code was highlighted with Source Code Highlighter.

    Ну и так далее. Как говорится — нет предела совершенству!

    Всё! Пирог слеплен, теперь в духовку (т.е. в ваш мозг) на пол-часа, и — готово. Приятного аппетита!

    Как это есть


    Ну и конкретный пример использования:
    // Создадим простенький класс
    var Num = function(x) {
      this.x = x;
    }
    // Опишем прототип
    Num.prototype.x = null;
    Num.prototype.getX = function() {
      return this.x;
    };
    Num.prototype.setX = function(x) {
      return this.x = x;
    }

    // Создадим экземпляр
    var t = new Num(6);

    // Добавим слушателя after
    t.after("getX", function(){
      alert('Поздравляем! Ваш X == ' + this.x + '!');
    })

    // Добавим слушателя after
    t.after("getX", function(){
      alert('И ещё раз поздравляем!');
    })

    // Добавим слушателя before, с проверкой
    var f = function(x){
      if (x < 0 || x > 10) {
        alert('Нет! Значение должно быть в отрезке [0, 10]');
        return false;
      }
    }
    t.before("setX", f)

    // поиграем:
    t.getX(); // Поздравляем! Ваш X == 6! -> 'И ещё раз поздравляем! -> вызов базового getX(...)
    t.setX(100); // Нет! Значение должно быть в отрезке [0, 10] -> базовый setX(100) - не вызвался
    alert(t.x); // 6
    t.setX(4); // Всё ОК, вызывается базовый метод setX(4)
    alert(t.x); // 4
    t.removeBefore("setX", f) // удаляем нашу проверку f(...)
    t.setX(100); // всё ОК, сработал базовый setX(100)
    alert(t.x); // 100


    * This source code was highlighted with Source Code Highlighter.

    Кстати, весь приведённый код — кросс-браузерный, таков мой принцип работы.

    Вывод


    Надеюсь что моя стряпня вам понравилась. Пишите, буду рад ответить на вопросы. И поверьте — JavaScript действительно очень мощный, гибкий и класный язык программирования, хоть и скриптовый.

    Update: Decorator Mark II


    UPD: 09.06.2009
    Прошло время с написания статьи и я нашёл в моей реализации ошибки и недостатки. Но я не хочу перекраивать статью, и не хочу создавать новую. Поэтому встречайте здесь и сейчас: Decorator Mark II!

    1. Улучшен Function.method(...)
    // Теперь мы можем как задавать метод, так и доставать его
    Function.prototype.method = function(methodName, f) {
      if (typeof f != "undefined")
        this.prototype[methodName] = f;
      return this.prototype[methodName];
    }


    * This source code was highlighted with Source Code Highlighter.


    2. Улучшен метод restore(...)
    // Теперь мы можем достать как ближайшую "украшенную" ф-цию, так и самую первую в цепочке
    Function.method("restore", function(fullRestore){
      var ret = this.old || this;
      while (fullRestore && ret.old) {
        ret = ret.old;
      }
      return ret;
    })


    * This source code was highlighted with Source Code Highlighter.


    3. Добавлен метод decorateMethod(...)
    // специально для декорирования методов
    Function.method("decorateMethod", function(methodName, decorator){
      var f = this.method(methodName);
      if (!f)
        return null;
      f.name = methodName;
      f = f.decorate(decorator);
      return this.method(methodName, f);
    })


    * This source code was highlighted with Source Code Highlighter.


    4. (важно!) Исправлен/изменён метод decorate(...)
    Function.method("decorate", function(decorator){
      // Сохраняем исходную функцию
      var oldFunc = this;
      // Важно! теперь возможно нормальное пере-использование одного и того же декоратора.
      // Теперь мы его никак не трогаем и не изменяем, а создаём и возвращаем новую ф-цию.
      // Однако теперь декоратор первым аргументом всегда будет получать некий объект,
      // в котором -- original: oldFunc (оригинал) и decoratorInstance: f (настоящий декоратор)
      var f = function(){
        return decorator.apply(this, [{original: oldFunc, decoratorInstance: f}].concat([].slice.apply(arguments)))
      }
      // Сохраняем оригинал ф-ции - в decoratorInstance f
      f.old = oldFunc;
      // Восстанавливаем прототип и конструктор ф-ции.
      // Это необходимо для сохранения ф-ции как конструктора.
      f.prototype = this.prototype;
      f.prototype.constructor = f;
      // Восстанавливаем имя ф-ции. Откуда оно вообще берётся? Можно задать вручную.
      //Или см. выше, новый метод decorateMethod: в нём это задаётся.
      f.name = oldFunc.name;
      // возвращаем декоратор
      return f;
    })


    * This source code was highlighted with Source Code Highlighter.


    Если вы решите попробовать эти изменения (особенно касающиесь метода decorate(...)), то вам необходимо будет исправить примеры из этой статьи.
    К примеру:
    // По-старому
    function strictArgs() {
      var types = arguments;
      return function() {
        var params = arguments;
        //...
        return arguments.callee.old.apply(this, arguments);
      }
    }

    * This source code was highlighted with Source Code Highlighter.

    надо изменить на:
    // По-новому
    function strictArgs() {
      var types = arguments;
      return function(dScope) {
        var original = arguments[0].original; // или можно dScope.original
        var arguments = Array.prototype.slice.call(arguments, 1);
        var params = arguments;
        //...
        return original.apply(this, arguments);
      }
    }

    * This source code was highlighted with Source Code Highlighter.


    Мои извинения, чтиво стало ещё длинней :)

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 37

      +4
      Люди, я тут — новенький, поэтому могу немного «косячить». Извиняюсь за «много букв», но статья по-моему завершена по своей структуре.

      И ещё: я не знаю как, но очень хотелось бы перенести её в блог «Javascript»
        0
        Спасибо всем за карму. Перенёс в Javascript.
        0
        что-то как-то все чересчур сложно )

        Ext.override(Function, {
          decorate: function(fPre, fPost) {
            var _this = this;
            return function () {
              if (fPre) fPre.call(this, arguments);
              var res = _this.apply(this, arguments);
              if (fPost) fPost.call(this, res, arguments);
              return res;
            };
          }
        });


        >>> var add = function (a, b) {return a+b;}
        >>> add1 = add.decorate(function(args){console.info('function called with args:', args)}, function(res){console.info('end returned:',res)})
        function()
        >>> add1(2,5)
        function called with args: [2, 5]
        end returned: 7
        7
          0
          Согласен, в чём-то у меня сложней, чем у ExtJS :)

          Но во-первых, я излагал материал в развёрнутом виде, для читабельности.

          А во-вторых, мой механизм является более универсальным:
          вот смотрите, код Ext'а использует _this, который просто вызывается внутри декоратора. В то время, как мой декоратор имеет внутри себя ссылку на оригинальную ф-цию, что важно. Мой декоратор по сути полностью «оборачивает» исходную ф-цию, и манипулирует ей внутри себя как хочет. В то время, как Ext-овский вариант просто добавляет вызовы «до» и «после».

          Ну и в моём варианте я могу не просто «навешивать» вызовы «до» и «после», но и убирать их. И это не вызовет code mess.
            0
            дело не в Ext, это просто сокращенная запись

            Function.prototype.decorate = function () ...
              0
              А, вы про сам Ext.override?

              Так ведь приведённый мой Function.method(...) — в точности то же самое, только аргументы передаются не Object'ом, как в Ext.override, а именнем+значением.
                0
                ну остальной код я сам придумал, а не взял из Ext )
                  0
                  то есть, если точнее, то когда-то нагуглил кажется тут szeged2008.drupalcon.org/files/JS%20components.pdf
                    0
                    А, вот оно что :)
                    В зачатках мой код был очень похож на ваш. Но чем дальше в лес, тем толще партизаны.

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

                    Глянул в тот же Ext, на объект Observable, и понял, что необходимо иметь addListener и removeListener.

                    Ну и идея пошла дальше Ж)
              0
              Да, и моя реализация before('smth', ...) — может отменить выполнение основной ф-ции )
              0
              Отличная вещь.
              С помощью этого можно трекать вызовы в браузерах, где нету нормальных JS отладчиков. Хотя, помоему, автор задумывал её для другого :)
                0
                Ели честно, то ноги у идеи растут именно оттуда, откуда вы сказали :)
                Мне надо было сделать debug вызова некоторых ф-ций в IE. Можно, конечно, просто «остановить» поток коммандой debugger. Но что если в браузере нет отладчика, или нет доступа к исходному коду?
                Вообщем, если эта статья народу понравится, я мог бы подумать над применениями моего кода.
                Собственно, можете предлагать, а я смогу это учесть :)
                  0
                  Про исходный код вы что-то не то говорите. В чужом можно точки остановки ставить.
                    0
                    Можно. Но что, если у вас нет возможности их ставить? Например, у вас — Internet Explorer 6, без script debugger-плагина. Или Opera 8.5
                      0
                      Вот и я про то же. Если есть дебаггер, то нет проблемы доступа к исходному коду. В любом другом случае, если нет доступа к исходному коду и нет дебаггера, то ваш метод тоже не поможет.
                        0
                        Ну, ключевое слово debugger может ничего и не даст, если отсутствует хоть какой-то debugger. Однако в любом браузере можно написать нечто следующее:
                        javascript:f=function(x){alert(x)};f=f.decorate(function(x){alert('decorator: '+x); arguments.callee.old.apply(this, arguments)});f(5);

                        * This source code was highlighted with Source Code Highlighter.

                        прямо в адресной строке любого браузера. Так и дебажить, простым alert() :-)
                          0
                          Век живи — век учись… Спасибо.
                +1
                Отличная статья. Не без недочетов, но тем не менее, плюс и топику, и в карму.

                Недостатки (имхо):
                1) Изменение базовых объектов (Function, Array, Object, etc.) — порочная практика.
                Пример:
                Вот вы объявили метод Function.prototype.method, который расчитывает, что к нему данные придут в порядке «имя нового метода», function. А кто-то другой написал метод с таким же названием, только ожидает данных в обратном порядке. Вам и этому кому-то все равно — вы-то разберетесь, что происходит. А кто-нибудь третий, подключив и ваш, и чужой код, оба ваших кода, получит кашу, и будет не знать, куда бежать.

                2) Путица вида «класс, класс Function, ой, в Javascript нет классов» в пункте «Нырнем на пару метров вглубь». Лучше бы определиться.
                  0
                  >> 1) Изменение базовых объектов (Function, Array, Object, etc.) — порочная практика.
                  Совершенно согласен. Но всё-таки Function.method, Function.decorate и Function.restore — красиво. От остального можно избавиться.

                  >> 2) Путица вида «класс, класс Function, ой, в Javascript нет классов» в пункте «Нырнем на пару метров вглубь». Лучше бы определиться.
                  Извиняюсь, мне просто было удобно использовать слово «класс». Википедия пишет так:
                  Javascript uses prototypes instead of classes for defining object properties, including methods, and inheritance. It is possible to simulate many class-based features with prototypes in Javascript.
                  А значит классов здесь никаких нет :)
                  +2
                  Вы со словом «класс» создаёте сильную путаницу в тексте. Даже если отвлечься от того, что в JS нет классов, саму по себе функцию (Object, Function, YourClassName) обычно не называют классом, а называют конструктором класса. Сам класс — это нечто более абстрактное: совокупность конструктора, его прототипа и всяких других вещей.
                    +1
                    Ну хорошо, в будущем я постараюсь не использовать слово "класс" в контексте Javascript :)

                    Я навёл справки в документации ECMA-262: там используются следующие термины для функций:
                    1) Метод — функция, хранимая в свойстве объекта.
                    2) Конструктор — объект типа Function, который создаёт и инициализирует объекты.
                    3) А также функцию можно называть объектом типа Function.

                    Заметьте: просто конструктор, но не конструктор класса, и это принципиально.

                    Почитать ещё можно тут: http://javascript.ru/ecma/part4
                    0
                    У вас какая-то путаница образовалась. В коде функции strictArgs вы пишете «var types = arguments», а в пояснении ниже «var types = [].slice.apply(arguments)».
                      +1
                      Спасибо, что заметили. Сначала я использовал [].slice.apply(arguments), но потом подумал, что это излишне, и заменил на просто arguments. Но получается, что заменил не везде :-) Я это исправил.
                        0
                        Нет там путаницы. Это аргументы разных функций (обратите внимание на то что возвращается тоже ф-ция)
                          0
                          Пардон, не так понял
                        0
                        Слушание и модификация вызовов отдельной функции (без изменения прототипа) — в моём проекте Ajaxpect: code.google.com/p/ajaxpect/.
                        Описание использования Ajaxpect для профайлинга — на блоге Agile Ajax: www.pathf.com/blogs/2007/07/ajaxpect-aop-fo/
                          +1
                          Раз уж вы дали здесь рекламу своего фреймворка Ajaxpect, напишу на него свою небольшую критику :-)
                          Я почитал ваш код и выделю следующее:
                          Плюсы:
                          1) Отвязанность от прототипов стандартных объектов, вроде Object и Function (всё делается через объект Ajaxpect)
                          2)Продвинутый и довольно удобный поиск методов в объекте:
                          * можно задать имя метода
                          * можно задать функцию-фильтр, которая возвращает true, если переданное в неё имя метода соответствует неким условиям
                          * можно задать RegExp, который также фильтрует имена методов объекта
                          Теперь минусы:
                          1) Самое плохое — нет возможности снять навешенный декоратор. Сейчас у вас это можно сделать только внутри самого декоратора, но довольно хитро. А если навесить декоратор дважды, то такая возможность и вовсе исчезнет.
                          2) Само разделение методов addBefore, addAfter и addAroundнедоработано. К примеру:
                          * addAfter легко выкидывается и реализуется через addAround, т.к. вы передаёте в декоратор и оригинальную ф-цию, и её аргументы
                          * addBefore — вы сначала выполняете функцию-декоратор, которая по сути возвращает изменённые arguments в оригинальную ф-цию, и тут же вызываете оригинальную ф-цию. Декоратор «before» должен иметь возможность остановить выполнение оригинальной ф-ции.

                          Это основное, что я заметил.

                          Теперь давайте сравним с моим творением — у меня всё с точностью до наоборот:
                          Минусы:
                          1) Я «засорил» стандартный объект Function.
                          2) У меня самый простой способ поиска метода: по имени.
                          3) Да, и у меня нет в явном виде вашего addAround. Но он легко реализуем у меня через Function.decorate(...).
                          Но плюсы:
                          1) Есть removeBefore(...) и removeAfter(...).
                          2) «Хорошая» организация стэка listener'ов
                          3) «Хорошая» реализация addBefore: если хоть один из addBefore-listener'ов возвращает false, оригинальная ф-ция не выполнится.

                          По мощности моя реализация равна вашей. но я бы вам советовал разложить всё по полочкам и учесть мои замечания. К тому же, я написал эту статью в ознакомительных целях. По сути мой код можно оформить в отдельный framework, но я пока не хочу этого делать :-)
                            0
                            Критиковал я Ajaxpect версии 0.9.0, скачал которую в 11:30 UTC time с code.google.com/p/ajaxpect/, чтобы потом на меня не наехали, что ничего такого в коде нет :)
                              0
                              Спасибо за конструктивные замечания и предложения :-)

                              Вообще Javascript'ом я серьёзно никогда не занимался, а framework «сам получился».

                              Изначально код был опубликован в блоге в контексте критики возможностей Java AOP в сравнении с динамическими языками (на примере Javascript).

                              После того, как я увидел, что «незнакомые люди» пользуются моим кодом — решил, что будет лучше избавить их от необходимости делать cut-and-paste с блога, а заодно слегка усилил код по сравнению с первоначальным вариантом (ещё 2003 года).
                                +2
                                Ясно. Ну значит есть повод ещё разок обновить код ;-)
                          0
                          Класс! С помощь вашего метода можно реализовать аспектно-ориентированный подход для Javascript.
                            0
                            Угу. Но я побоялся использовать словосочетание «аспектно-ориентированный подход» в своей статье, т.к. ничего об этом в ней не писал.
                              0
                              Только прочитав название статьи, сразу подумал про АОП.
                            0
                            Спасибо за конструктивные замечания и предложения :-)

                            Вообще Javascript'ом я серьёзно никогда не занимался, а framework «сам получился».

                            Изначально код был опубликован в блоге в контексте критики возможностей Java AOP в сравнении с динамическими языками (на примере Javascript).

                            После того, как я увидел, что «незнакомые люди» пользуются моим кодом — решил, что будет лучше избавить их от необходимости делать cut-and-paste с блога, а заодно слегка усилил код по сравнению с первоначальным вариантом (ещё 2003 года).

                              0
                              Дико извиняюсь, не туда запостил — это был ответ на комментарий Kottenator по поводу Ajaxpect.
                              0
                              статья потрясная! узнал новые вещи про js. очень полезно!
                                0
                                чувак, ты крут…
                                очень интересная статья, спасибо
                                ЗЫ Javascript меня не перестает удивлять…

                                Only users with full accounts can post comments. Log in, please.