Javascript: Розширення функції


94

Основна причина, чому я хочу це, полягає в тому, що я хочу розширити свою функцію ініціалізації.

Щось на зразок цього:

// main.js

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

// extend.js

function extends init(){
    doSomethingHereToo();
}

Тому я хочу розширити функцію, як я розширюю клас у PHP.

І я хотів би також розширити його з інших файлів, тому, наприклад, у мене є оригінальна функція init main.jsі розширена функція в extended.js.



Відповіді:


103

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

Але ось буквальна відповідь:

Якщо ви десь присвоюєте ці функції якомусь властивості, ви можете обернути оригінальну функцію та замість цього помістити заміну у властивість:

// 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 = (function() {
    var publicSymbols = {};

    publicSymbols.init = init;
    function init() {
    }

    return publicSymbols;
})();

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

Але є такі кращі способи зробити це. Як, наприклад, надання засобів реєстрації initфункцій.

// In main.js
var MyLibrary = (function() {
    var publicSymbols = {},
        initfunctions = [];

    publicSymbols.init = init;
    function init() {
        var funcs = initFunctions;

        initFunctions = undefined;

        for (index = 0; index < funcs.length; ++index) {
            try { funcs[index](); } catch (e) { }
        }
    }

    publicSymbols.addInitFunction = addInitFunction;
    function addInitFunction(f) {
        if (initFunctions) {
            // Init hasn't run yet, rememeber it
            initFunctions.push(f);
        }
        else {
            // `init` has already run, call it almost immediately
            // but *asynchronously* (so the caller never sees the
            // call synchronously)
            setTimeout(f, 0);
        }
    }

    return publicSymbols;
})();

(Багато з вищезазначеного можна було написати трохи компактніше, але я хотів використовувати чіткі імена, publicSymbolsа не мій звичайний pubsабо анонімний об’єктний літерал. Ви можете писати це набагато компактніше, якщо хочете мати анонімні функції, але я не багато уваги до анонімних функцій .)


Дякую за чудову відповідь. Моя проблема з другим прикладом полягає в тому, що мені може знадобитися результат функції, яку я розширюю.
Герхард Давідс

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?
Суніл

+1 Дякую. Це дуже зручно, якщо ви хочете виправити якийсь сторонній плагін, не змінюючи оригінальний js.
GFoley83,

1
Не знаю, як використовувати його з функціями, які очікують параметрів і повернутих значень.
Герфрід

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

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

Використовуйте extensionFunction.js

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

Але у вашому конкретному випадку простіше розширити глобальну функцію завантаження:

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

Мені насправді дуже подобається ваше запитання, це змушує задуматися про різні варіанти використання.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.