Переопределение функции JavaScript при обращении к оригиналу


160

У меня есть функция, a()которую я хочу переопределить, но оригинал a()должен быть выполнен в порядке, зависящем от контекста. Например, иногда, когда я генерирую страницу, я хочу переопределить так:

function a() {
    new_code();
    original_a();
}

а иногда так:

function a() {
    original_a();
    other_new_code();
}

Как я могу получить это original_a()из-за переезда a()? Это вообще возможно?

Пожалуйста, не предлагайте альтернативы перегрузке таким образом, я знаю многих. Я спрашиваю об этом конкретно.

Ответы:


179

Вы могли бы сделать что-то вроде этого:

var a = (function() {
    var original_a = a;

    if (condition) {
        return function() {
            new_code();
            original_a();
        }
    } else {
        return function() {
            original_a();
            other_new_code();
        }
    }
})();

Объявление original_aвнутри анонимной функции не дает ей загромождать глобальное пространство имен, но оно доступно во внутренних функциях.

Как упоминается в комментариях Nerdmaster, обязательно добавьте в ()конце. Вы хотите вызвать внешнюю функцию и сохранить результат (одну из двух внутренних функций) a, а не хранить саму внешнюю функцию a.


25
Для любых идиотов, таких как я, - обратите внимание на «()» в конце - без этого он возвращает внешнюю функцию, а не внутренние функции :)
Nerdmaster

@ Nerdmaster Спасибо за указание на это. Я добавил примечание, чтобы, надеюсь, помочь людям заметить.
Мэтью Крамли

5
Вау, я попытался сделать это без пространства имен, и у меня переполнение стека, смеется.
Госукиви

Я думаю, что результат будет таким же, если первые и последние скобки будут удалены из функции. Вроде отсюда (functi..и }). Простое выполнение }();даст тот же результат, что и (function{})()1-й набор скобок, потому что вы не можете просто объявить анонимное выражение функции, которое вам нужно назначить. Но, делая (), вы ничего не определяете, просто возвращая функцию.
Мухаммед Умер

@MuhammadUmer Вы правы, что круглые скобки вокруг выражения функции не нужны. Я включил их, потому что без них, если вы просто посмотрите на первые пару строк, похоже (по крайней мере мне), что вы назначаете внешнюю функцию напрямую a, не вызывая ее и сохраняя возвращаемое значение. Скобки дают мне понять, что происходит что-то еще.
Мэтью Крамли

88

Шаблон Proxy может помочь вам:

(function() {
    // log all calls to setArray
    var proxied = jQuery.fn.setArray;
    jQuery.fn.setArray = function() {
        console.log( this, arguments );
        return proxied.apply( this, arguments );
    };
})();

Вышесказанное оборачивает свой код в функцию, чтобы скрыть переменную «прокси». Он сохраняет метод setArray в jQuery и закрывает его. Затем прокси регистрирует все вызовы метода и делегирует вызов оригиналу. Использование apply (this, arguments) гарантирует, что вызывающая сторона не сможет заметить разницу между оригинальным и проксируемым методом.


10
на самом деле. использование «применять» и «аргументы» делает это гораздо более надежным, чем другие ответы
Роберт Леви

Обратите внимание, что этот шаблон не требует jQuery - они просто используют одну из функций jQuery в качестве примера.
Кев

28

Спасибо, ребята, шаблон прокси действительно помог ..... На самом деле я хотел вызвать глобальную функцию foo .. На некоторых страницах мне нужно выполнить некоторые проверки. Итак, я сделал следующее.

//Saving the original func
var org_foo = window.foo;

//Assigning proxy fucnc
window.foo = function(args){
    //Performing checks
    if(checkCondition(args)){
        //Calling original funcs
        org_foo(args);
    }
};

Спасибо, это действительно помогло мне


3
Следуя шаблону прокси , в некоторых случаях будет важно реализовать эту деталь, которой нет в коде этого Ответчика: вместо org_foo(args)вызова org_foo.call(this, args). Это поддерживается так, thisкак это было бы, когда window.foo вызывает нормально (без прокси). Посмотри этот ответ
cellepo

10

Вы можете переопределить функцию, используя такую ​​конструкцию, как:

function override(f, g) {
    return function() {
        return g(f);
    };
}

Например:

 a = override(a, function(original_a) {
      if (condition) { new_code(); original_a(); }
      else { original_a(); other_new_code(); }
 });

Изменить: Исправлена ​​опечатка.


+1 Это намного более читабельно, чем другие примеры шаблонов прокси!
Му Разум

1
... но если я не ошибаюсь, это ограничивается функциями с нулевым аргументом или, по крайней мере, некоторым заранее заданным числом аргументов. Есть ли способ обобщить его на произвольное количество аргументов без потери читабельности?
Му Mind

@MuMind: да, но правильно использую шаблон прокси - см. Мой ответ .
Дан Даскалеску

4

Передавая произвольные аргументы:

a = override(a, function(original_a) {
    if (condition) { new_code(); original_a.apply(this, arguments) ; }
    else { original_a.apply(this, arguments); other_new_code(); }
});

1
не аргументы относятся к original_aхотя?
Джонатан.

2

Ответ, который дает @Matthew Crumley, заключается в использовании немедленно вызванных выражений функций, чтобы закрыть старую функцию 'a' в контексте выполнения возвращаемой функции. Я думаю, что это был лучший ответ, но лично я предпочел бы передать функцию «а» в качестве аргумента IIFE. Я думаю, что это более понятно.

   var a = (function(original_a) {
        if (condition) {
            return function() {
                new_code();
                original_a();
            }
        } else {
            return function() {
                original_a();
                other_new_code();
            }
        }
    })(a);

1

Приведенные выше примеры неправильно применяются thisили передаются argumentsкорректно в функцию переопределения. Подчеркивание _.wrap () оборачивает существующие функции, применяет thisи передает argumentsправильно. Смотрите: http://underscorejs.org/#wrap


0

У меня был какой-то код, написанный кем-то другим, и я хотел добавить строку в функцию, которую я не смог найти в коде. Поэтому в качестве обходного пути я хотел переопределить это.

Ни одно из решений не помогло мне.

Вот что сработало в моем случае:

if (typeof originalFunction === "undefined") {
    originalFunction = targetFunction;
    targetFunction = function(x, y) {
        //Your code
        originalFunction(a, b);
        //Your Code
    };  
}

0

Я создал небольшой помощник для аналогичного сценария, потому что мне часто приходилось переопределять функции из нескольких библиотек. Этот помощник принимает «пространство имен» (контейнер функции), имя функции и переопределяющую функцию. Он заменит исходную функцию в указанном пространстве имен новой.

Новая функция принимает исходную функцию в качестве первого аргумента, а исходные функции - в качестве остальных. Это сохранит контекст каждый раз. Он также поддерживает функции void и non void.

function overrideFunction(namespace, baseFuncName, func) {
    var originalFn = namespace[baseFuncName];
    namespace[baseFuncName] = function () {
        return func.apply(this, [originalFn.bind(this)].concat(Array.prototype.slice.call(arguments, 0)));
    };
}

Использование, например, с Bootstrap:

overrideFunction($.fn.popover.Constructor.prototype, 'leave', function(baseFn, obj) {
    // ... do stuff before base call
    baseFn(obj);
    // ... do stuff after base call
});

Я не создавал никаких тестов производительности, хотя. Это может добавить некоторые нежелательные накладные расходы, которые могут или не могут иметь большое значение, в зависимости от сценариев.


0

По моему мнению, верхние ответы не читабельны / не поддерживаются, а другие ответы не связывают должным образом контекст. Вот удобочитаемое решение с использованием синтаксиса ES6 для решения обеих этих проблем.

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};

Но использование синтаксиса ES6 делает его довольно ограниченным в использовании. У вас есть версия, которая работает в ES5?
Eitch

0

Так что мой ответ оказался решением, которое позволяет мне использовать переменную _this, указывающую на исходный объект. Я создаю новый экземпляр «Квадрата», но ненавидел то, как «Квадрат» генерировал его размер. Я думал, что это должно следовать моим конкретным потребностям. Однако для того, чтобы сделать это, мне потребовалось, чтобы в квадрате была обновленная функция «GetSize» с внутренними компонентами этой функции, вызывающими другие функции, уже существующие в квадрате, такие как this.height, this.GetVolume (). Но для этого мне нужно было делать это без каких-либо сумасшедших взломов. Так вот мое решение.

Какой-то другой инициализатор объекта или вспомогательная функция.

this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(
  this.viewerContainer)
var viewer = this.viewer;
viewer.updateToolbarButtons =  this.updateToolbarButtons(viewer);

Функция в другом объекте.

updateToolbarButtons = function(viewer) {
  var _viewer = viewer;
  return function(width, height){ 
blah blah black sheep I can refer to this.anything();
}
};

0

Не уверен, что это будет работать при любых обстоятельствах, но в нашем случае мы пытались переопределить describeфункцию в Jest, чтобы мы могли проанализировать имя и пропустить весь describeблок, если он удовлетворял некоторым критериям.

Вот что сработало для нас:

function describe( name, callback ) {
  if ( name.includes( "skip" ) )
    return this.describe.skip( name, callback );
  else
    return this.describe( name, callback );
}

Здесь важны две вещи:

  1. Мы не используем функцию стрелки () =>.

    Функции стрелок меняют ссылку на, thisи нам нужно, чтобы это было в файле this.

  2. Использование this.describeи this.describe.skipвместо всего describeи describe.skip.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.