Javascript: estenda uma função

94

A principal razão pela qual eu quero isso é que eu quero estender minha função de inicialização.

Algo assim:

// main.js

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

// extend.js

function extends init(){
    doSomethingHereToo();
}

Então, eu quero estender uma função como estendo uma classe em PHP.

E eu gostaria de estendê-lo de outros arquivos também, então, por exemplo, tenho a função init original em main.jse a função estendida em extended.js.

Adão
fonte
Atualização do link: jondavidjohn.com/extend-javascript-functions
NETCreator Hosting

Respostas:

103

Com uma visão mais ampla do que você está realmente tentando fazer e do contexto em que está fazendo isso, tenho certeza de que poderíamos dar uma resposta melhor do que a resposta literal à sua pergunta.

Mas aqui está uma resposta literal:

Se estiver atribuindo essas funções a alguma propriedade em algum lugar, você pode encapsular a função original e colocar sua substituição na propriedade:

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

Se suas funções ainda não estiverem em um objeto, você provavelmente gostaria de colocá-las lá para facilitar o acima. Por exemplo:

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

Mas existem tais melhores maneiras de fazer isso. Como, por exemplo, fornecer um meio de registrar initfunções.

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

(Muito do que foi dito acima poderia ser escrito de forma um pouco mais compacta, mas eu queria usar nomes claros como em publicSymbolsvez de meu pubsliteral de objeto anônimo normal . Você pode escrever de forma muito mais compacta se quiser ter funções anônimas, mas eu não t muito cuidado com funções anônimas .)

TJ Crowder
fonte
Obrigado por uma ótima resposta. Meu problema com o segundo exemplo é que posso precisar do resultado da função que estou estendendo.
Gerhard Davids
64

Existem várias maneiras de fazer isso, depende de qual é o seu propósito, se você quiser apenas executar a função também e no mesmo contexto, você pode usar .apply():

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

Se você quiser substituí-lo por um mais recente init, ficaria assim:

function init(){
  doSomething();
}
//anytime later
var old_init = init;
init = function() {
  old_init.apply(this, arguments);
  doSomethingHereToo();
};
Nick Craver
fonte
2
Às vezes, você pode querer o .callmétodo em vez de .apply. Veja esta pergunta StackOverflow.
MrDanA
@ Nick, achei seu exemplo de JavaScript para estender uma função existente muito útil, mas estava curioso para saber como essa mesma coisa seria feita por meio do jQuery?
Domingo
+1 Obrigado. Isso é realmente útil se você deseja corrigir algum plugin de terceiros sem modificar o js original.
GFoley83
1
Não tenho certeza de como usá-lo com funções que esperam parâmetros e valores de retorno.
Gerfried
5

Os outros métodos são ótimos, mas não preservam nenhuma função de protótipo anexada ao init. Para contornar isso, você pode fazer o seguinte (inspirado na postagem de Nick Craver).

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

Outra opção poderia ser:

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 );
Sérgio
fonte
0

Isso é muito simples e direto. Olhe o código. Tente compreender o conceito básico por trás da extensão javascript.

Primeiro, vamos estender a função 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
}

Você pode estender esta função criando uma função filha da seguinte maneira

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
}

Agora você pode usar a função Criança da seguinte maneira,

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

Também podemos criar função Javascript estendendo classes Javascript, como esta.

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

Vamos estender esta classe com a função Child assim,

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

Novamente, você pode usar a função filho da seguinte maneira para obter resultados semelhantes,

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

Javascript é uma linguagem muito fácil. Podemos fazer quase tudo. Feliz JavaScripting ... Espero ter podido lhe dar uma ideia para usar no seu caso.

Fila de mercado
fonte
-1

Use extendFunction.js

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

Mas, no seu caso específico, é mais fácil estender a função onload global:

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

Na verdade, gostei muito da sua pergunta, está me fazendo pensar sobre diferentes casos de uso.

Para eventos javascript, você realmente deseja adicionar e remover manipuladores - mas para extendFunction, como você poderia remover a funcionalidade posteriormente ? Eu poderia facilmente adicionar um método .revert às funções estendidas, portanto init = init.revert(), retornaria a função original. Obviamente, isso pode levar a um código muito ruim, mas talvez permita que você faça algo sem tocar em uma parte estrangeira da base de código.

Devin G Rhode
fonte