Перезапис функції 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
Нічого собі, я спробував це зробити без простору імен, і я отримав переповнення стека, lol.
gosukiwi

Я думаю, що результат був би однаковим, якщо перші та останні дужки будуть вилучені з функції. Як звідси (functi..і }). Просто виконання }();дасть такий же результат, як використовується (function{})()перший набір дужок, оскільки ви не можете просто оголосити, анонімне, вираження функції, яке вам потрібно призначити. Але, роблячи (), ви нічого не визначаєте, просто повертаєте функцію.
Мухаммед Умер

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

Вищезгадане перетворює свій код у функцію, щоб приховати "proxied"-змінну. Він зберігає метод jQuery setArray під час закриття та перезаписує його. Потім проксі реєструє всі виклики методу та делегує виклик оригіналу. Використання застосовувати (це, аргументи) гарантує, що абонент не зможе помітити різницю між оригінальним та проксі-методом.


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

Thnx це мені справді допомогло


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 Це набагато легше читати, ніж інші приклади проксі-сервера!
Mu Mind

1
... але якщо я не помиляюся, це обмежується функціями з нульовим аргументом або хоча б якоюсь заданою кількістю аргументів. Чи є якийсь спосіб узагальнити це до довільної кількості аргументів, не втрачаючи читабельності?
Mu 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' у контексті виконання повернутої функції. Я думаю, що це була найкраща відповідь, але особисто я вважаю за краще передавати функцію '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

Я створив маленького помічника для подібного сценарію, тому що мені часто потрібно було перекривати функції з декількох бібліотек. Цей помічник приймає "простір імен" (контейнер функцій), ім'я функції та функцію, що перекриває. Він замінить оригінальну функцію у згаданому просторі імен новою.

Нова функція приймає оригінальну функцію як перший аргумент, а оригінальну аргументацію функції як решту. Він буде зберігати контекст кожного разу. Він також підтримує недійсні та недійсні функції.

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

Отже, моя відповідь виявилася рішенням, яке дозволяє мені використовувати цю змінну, що вказує на оригінальний об'єкт. Я створюю новий екземпляр "Квадрат", проте я ненавидів те, як "Квадрат" генерував його розмір. Я вважав, що це має відповідати моїм конкретним потребам. Однак для цього мені знадобився квадрат, щоб мати оновлену функцію "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.