Javascript: расширение функции


97

Основная причина, по которой я хочу это, заключается в том, что я хочу расширить свою функцию инициализации.

Что-то вроде этого:

// main.js

window.onload = init();
function init(){
     doSomething();
}

// extend.js

function extends init(){
    doSomethingHereToo();
}

Поэтому я хочу расширить функцию, как расширяю класс в PHP.

И я хотел бы расширить его и из других файлов, поэтому, например, у меня есть исходная функция init main.jsи расширенная функция extended.js.



Обновление ссылки: jondavidjohn.com/extend-javascript-functions
Хостинг

Ответы:


106

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

Но вот дословный ответ:

Если вы где-то назначаете эти функции какому-либо свойству, вы можете обернуть исходную функцию и вместо этого поместить свою замену в свойство:

// Original code in main.js
var theProperty = init;

function init(){
     doSomething();
}

// Extending it by replacing and wrapping, in extended.js
theProperty = (function(old) {
    function extendsInit() {
        old();
        doSomething();
    }

    return extendsInit;
})(theProperty);

Если ваших функций еще нет на объекте, вы, вероятно, захотите разместить их там, чтобы облегчить описанное выше. Например:

// In main.js
var MyLibrary = {
    init: function init() {
    }
};

// In extended.js
(function() {
    var oldInit = MyLibrary.init;
    MyLibrary.init = extendedInit;
    function extendedInit() {
        oldInit.call(MyLibrary); // Use #call in case `init` uses `this`
        doSomething();
    }
})();

Но есть способы сделать это лучше. Как, например, предоставление средств регистрации initфункций.

// In main.js
var MyLibrary = (function() {
    var initFunctions = [];
    return {
        init: function init() {
            var fns = initFunctions;
            initFunctions = undefined;
            for (var index = 0; index < fns.length; ++index) {
                try { fns[index](); } catch (e) { }
            }
        },
        addInitFunction: function addInitFunction(fn) {
            if (initFunctions) {
                // Init hasn't run yet, remember it
                initFunctions.push(fn);
            } else {
                // `init` has already run, call it almost immediately
                // but *asynchronously* (so the caller never sees the
                // call synchronously)
                setTimeout(fn, 0);
            }
        }
    };
})();

Здесь, в 2020 году (или в любое время после ~ 2016 года), это можно записать более компактно:

// In main.js
const MyLibrary = (() => {
    let initFunctions = [];
    return {
        init() {
            const fns = initFunctions;
            initFunctions = undefined;
            for (const fn of fns) {
                try { fn(); } catch (e) { }
            }
        },
        addInitFunction(fn) {
            if (initFunctions) {
                // Init hasn't run yet, remember it
                initFunctions.push(fn);
            } else {
                // `init` has already run, call it almost immediately
                // but *asynchronously* (so the caller never sees the
                // call synchronously)
                setTimeout(fn, 0);
                // Or: `Promise.resolve().then(() => fn());`
                // (Not `.then(fn)` just to avoid passing it an argument)
            }
        }
    };
})();

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

64

Есть несколько способов сделать это, это зависит от вашей цели, если вы просто хотите выполнить функцию и в том же контексте, вы можете использовать .apply():

function init(){
  doSomething();
}
function myFunc(){
  init.apply(this, arguments);
  doSomethingHereToo();
}

Если вы хотите заменить его на более новый init, он будет выглядеть так:

function init(){
  doSomething();
}
//anytime later
var old_init = init;
init = function() {
  old_init.apply(this, arguments);
  doSomethingHereToo();
};

2
Иногда вам может понадобиться .callметод вместо .apply. См. Этот вопрос на StackOverflow.
MrDanA

@Nick, я нашел ваш пример JavaScript для расширения существующей функции очень полезным, но мне было любопытно, как то же самое можно сделать с помощью jQuery?
Sunil

+1 Спасибо. Это действительно удобно, если вы хотите исправить какой-либо сторонний плагин, не изменяя исходный js.
GFoley83

1
Не знаю, как использовать его с функциями, которые ожидают параметры и возвращают значения.
Gerfried

5

Другие методы хороши, но они не сохраняют никаких функций-прототипов, прикрепленных к init. Чтобы обойти это, вы можете сделать следующее (на основе сообщения Ника Крейвера).

(function () {
    var old_prototype = init.prototype;
    var old_init = init;
    init = function () {
        old_init.apply(this, arguments);
        // Do something extra
    };
    init.prototype = old_prototype;
}) ();

5

Другой вариант может быть:

var initial = function() {
    console.log( 'initial function!' );
}

var iWantToExecuteThisOneToo = function () {
    console.log( 'the other function that i wanted to execute!' );
}

function extendFunction( oldOne, newOne ) {
    return (function() {
        oldOne();
        newOne();
    })();
}

var extendedFunction = extendFunction( initial, iWantToExecuteThisOneToo );

1

2017+ решение

Идея расширения функций исходит из функциональной парадигмы, которая изначально поддерживается начиная с ES6:

function init(){
    doSomething();
}

// extend.js

init = (f => u => { f(u)
    doSomethingHereToo();
})(init);

init();

Согласно озабоченности @TJCrowder дампом стека, браузеры сегодня справляются с ситуацией намного лучше. Если вы сохраните этот код в test.html и запустите его, вы получите

test.html:3 Uncaught ReferenceError: doSomething is not defined
    at init (test.html:3)
    at test.html:8
    at test.html:12

Строка 12: вызов инициализации, Строка 8: добавочный номер инициализации, Строка 3: неопределенный doSomething()вызов.

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


«Согласно озабоченности @TJCrowder по поводу дампа стека, браузеры сегодня справляются с этой ситуацией намного лучше». Действительно, и эти улучшения даже были кодифицированы в спецификации. (Я также мог бы избежать анонимной проблемы даже в 2011 году с именованными функциональными выражениями; хотя IE ошибается в IE8 и ниже, то, как он ошибается, было бы безвредно. Живите и учитесь. :-))
TJ Crowder

(И спасибо за очень добрые комментарии. Очень признательны!)
TJ Crowder

0

Это очень просто и понятно. Посмотрите на код. Попытайтесь понять основную концепцию расширения javascript.

Сначала давайте расширим функцию javascript.

function Base(props) {
    const _props = props
    this.getProps = () => _props

    // We can make method private by not binding it to this object. 
    // Hence it is not exposed when we return this.
    const privateMethod = () => "do internal stuff" 

    return this
}

Вы можете расширить эту функцию, создав дочернюю функцию следующим образом

function Child(props) {
    const parent = Base(props)
    this.getMessage = () => `Message is ${parent.getProps()}`;

    // You can remove the line below to extend as in private inheritance, 
    // not exposing parent function properties and method.
    this.prototype = parent
    return this
}

Теперь вы можете использовать функцию Child следующим образом:

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

Мы также можем создать функцию Javascript, расширив классы Javascript, как это.

class BaseClass {
    constructor(props) {
        this.props = props
        // You can remove the line below to make getProps method private. 
        // As it will not be binded to this, but let it be
        this.getProps = this.getProps.bind(this)
    }

    getProps() {
        return this.props
    }
}

Давайте расширим этот класс функцией Child следующим образом:

function Child(props) {
    let parent = new BaseClass(props)
    const getMessage = () => `Message is ${parent.getProps()}`;
    return { ...parent, getMessage} // I have used spread operator. 
}

Опять же, вы можете использовать функцию Child следующим образом, чтобы получить аналогичный результат:

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

Javascript - очень простой язык. Мы можем делать почти все. Удачного JavaScripting ... Надеюсь, я смог дать вам идею для использования в вашем случае.


-1

Используйте extendFunction.js

init = extendFunction(init, function(args) {
  doSomethingHereToo();
});

Но в вашем конкретном случае проще расширить глобальную функцию onload:

extendFunction('onload', function(args) {
  doSomethingHereToo();
});

Мне действительно нравится ваш вопрос, он заставляет задуматься о различных вариантах использования.

Для событий javascript вы действительно хотите добавлять и удалять обработчики, но для extendFunction как вы могли бы позже удалить функциональность? Я мог бы легко добавить метод .revert к расширенным функциям, чтобы init = init.revert()вернуть исходную функцию. Очевидно, что это может привести к довольно плохому коду, но, возможно, это позволит вам что-то сделать, не затрагивая чужую часть кодовой базы.

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