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

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

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

а иногда так:

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

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

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


person Kev    schedule 17.11.2008    source источник


Ответы (12)


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

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.

person Matthew Crumley    schedule 17.11.2008
comment
Для любых идиотов вроде меня - обратите особое внимание на () в конце - без этого он возвращает внешнюю функцию, а не внутренние функции :) - person Nerdmaster; 24.07.2012
comment
@Nerdmaster Спасибо, что указали на это. Я добавил примечание, чтобы, надеюсь, помочь людям заметить. - person Matthew Crumley; 25.07.2012
comment
Вау, я попытался сделать это без пространства имен, и у меня переполнение стека, лол. - person gosukiwi; 10.08.2012
comment
Я думаю, результат будет таким же, если убрать первую и последнюю круглые скобки из функции. Вроде отсюда (functi.. и }). Простое выполнение }(); приведет к тому же результату, поскольку используется (function{})() 1-й набор круглых скобок, потому что вы не можете просто объявить анонимное выражение функции, которое вы должны присвоить ему. Но, делая (), вы ничего не определяете, просто возвращая функцию. - person Muhammad Umer; 04.08.2013
comment
@MuhammadUmer Вы правы, что скобки вокруг выражения функции не нужны. Я включил их, потому что без них, если вы просто посмотрите на первые пару строк, это выглядит (по крайней мере, для меня), как будто вы назначаете внешнюю функцию напрямую a, а не вызываете ее и сохраняете возвращаемое значение. Скобки сообщают мне, что происходит еще кое-что. - person Matthew Crumley; 05.08.2013
comment
это правда, во время того, что я делал, после того, как я вернулся из потока своих мыслей, мне пришлось бы сделать мини-обзор того, что я делаю в коде, и очень запутался, почему эта функция назначается этому, а затем после прочтения построчно я бы увидел и запомнил о да .. - person Muhammad Umer; 06.08.2013

Вам может помочь шаблон прокси:

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

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

person PhilHoy    schedule 17.11.2008
comment
действительно. использование «применить» и «аргументы» делает это гораздо более надежным, чем другие ответы. - person Robert Levy; 21.05.2012
comment
Обратите внимание, что этот шаблон не требует jQuery - они просто используют одну из функций jQuery в качестве примера. - person Kev; 01.04.2020

Спасибо, ребята, шаблон прокси действительно помог ..... На самом деле я хотел вызвать глобальную функцию 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);
    }
};

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

person Naresh S    schedule 16.11.2009
comment
Следуя шаблону прокси, в некоторых случаях будет важно реализовать эту деталь, которая отсутствует в коде этого ответа: вместо org_foo(args) позвоните org_foo.call(this, args). Это поддерживает this, как это было бы при обычном вызове window.foo (без проксирования). См. этот ответ - person cellepo; 20.02.2016

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

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(); }
 });

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

person Hai Phan    schedule 31.01.2010
comment
+1 Это намного более читабельно, чем другие примеры шаблонов прокси! - person Mu Mind; 26.05.2011
comment
... но, если я не ошибаюсь, это ограничивается функциями с нулевым аргументом или, по крайней мере, некоторым заранее определенным количеством аргументов. Есть ли способ обобщить его на произвольное количество аргументов без потери удобочитаемости? - person Mu Mind; 26.05.2011
comment
@MuMind: да, но при правильном использовании шаблона прокси - см. мой ответ. - person Dan Dascalescu; 22.03.2014

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

a = override(a, function(original_a) {
    if (condition) { new_code(); original_a.apply(this, arguments) ; }
    else { original_a.apply(this, arguments); other_new_code(); }
});
person Edric Garran    schedule 31.01.2012
comment
неужели аргументы не относятся к original_a? - person Jonathan.; 06.04.2013

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

   var a = (function(original_a) {
        if (condition) {
            return function() {
                new_code();
                original_a();
            }
        } else {
            return function() {
                original_a();
                other_new_code();
            }
        }
    })(a);
person Rodrigo Hernandez    schedule 17.11.2016

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

person nevf    schedule 19.09.2012

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

Однако ни одно из решений не помогло мне.

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

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

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

Новая функция принимает исходную функцию в качестве первого аргумента, а аргументы исходной функции - в качестве остальных. Он всегда будет сохранять контекст. Он также поддерживает функции 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
});

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

person Zoltán Tamási    schedule 25.11.2015

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

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};
person James L.    schedule 12.09.2016
comment
Но использование синтаксиса ES6 делает его довольно ограниченным в использовании. У вас есть версия, которая работает в ES5? - person eitch; 30.11.2016

Итак, мой ответ оказался решением, которое позволяет мне использовать переменную _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();
}
};
person SteckDEV    schedule 30.08.2018

Не уверен, что это сработает при любых обстоятельствах, но в нашем случае мы пытались переопределить функцию 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.

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

person Joshua Pinter    schedule 05.06.2020