Javascript: ampliar una función

94

La razón principal por la que lo quiero es que quiero extender mi función de inicialización.

Algo como esto:

// main.js

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

// extend.js

function extends init(){
    doSomethingHereToo();
}

Entonces quiero extender una función como extiendo una clase en PHP.

Y también me gustaría extenderlo desde otros archivos, así que, por ejemplo, tengo la función init original main.jsy la función extendida en extended.js.

Adam Halasz
fuente
Actualización del enlace: jondavidjohn.com/extend-javascript-functions
Alojamiento NETCreator

Respuestas:

103

Con una visión más amplia de lo que realmente está tratando de hacer y el contexto en el que lo está haciendo, estoy seguro de que podríamos darle una mejor respuesta que la respuesta literal a su pregunta.

Pero aquí hay una respuesta literal:

Si está asignando estas funciones a alguna propiedad en algún lugar, puede ajustar la función original y colocar su reemplazo en la propiedad en su lugar:

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

Si sus funciones aún no están en un objeto, probablemente desee colocarlas allí para facilitar lo anterior. Por ejemplo:

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

Pero hay tales mejores maneras de hacer eso. Como, por ejemplo, proporcionar un medio para registrar initfunciones.

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

(Gran parte de lo anterior podría escribirse de forma un poco más compacta, pero quería usar nombres claros como en publicSymbolslugar de mi pubsobjeto literal habitual o anónimo. Puede escribirlo de forma mucho más compacta si desea tener funciones anónimas, pero yo no ' Tenga mucho cuidado con las funciones anónimas ).

TJ Crowder
fuente
Gracias por una gran respuesta. Mi problema con el segundo ejemplo es que podría necesitar el resultado de la función que estoy ampliando.
Gerhard Davids
64

Hay varias formas de hacerlo, depende de cuál sea su propósito, si solo desea ejecutar la función también y en el mismo contexto, puede usar .apply():

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

Si desea reemplazarlo por uno más nuevo init, se vería así:

function init(){
  doSomething();
}
//anytime later
var old_init = init;
init = function() {
  old_init.apply(this, arguments);
  doSomethingHereToo();
};
Nick Craver
fuente
2
A veces es posible que desee el .callmétodo en lugar de .apply. Vea esta pregunta de StackOverflow.
MrDanA
@Nick, encontré su ejemplo de JavaScript para extender una función existente muy útil, pero tenía curiosidad por saber cómo se haría esto mismo a través de jQuery.
Domingo
+1 gracias. Esto es realmente útil si desea parchear algún complemento de terceros sin modificar el js original.
GFoley83
1
No estoy seguro de cómo usarlo con funciones que esperan parámetros y devuelven valores.
Gerfried
5

Los otros métodos son geniales pero no conservan ninguna función prototipo adjunta a init. Para evitarlo, puede hacer lo siguiente (inspirado en la publicación 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
fuente
5

Otra opción podría 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
fuente
0

Esto es muy simple y directo. Mira el código. Intente comprender el concepto básico detrás de la extensión javascript.

Primero, extendamos la función 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
}

Puede extender esta función creando una función secundaria de la siguiente manera

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
}

Ahora puede usar la función Niño de la siguiente manera,

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

También podemos crear la función Javascript ampliando las clases de 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
    }
}

Extendamos esta clase con la función Child como esta,

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

Nuevamente, puede usar la función Niño de la siguiente manera para obtener un resultado similar,

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

Javascript es un lenguaje muy sencillo. Podemos hacer casi cualquier cosa. Happy JavaScripting ... Espero haber podido darte una idea para usar en tu caso.

Cola de mercado
fuente
-1

Utilice extendFunction.js

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

Pero en su caso específico, es más fácil extender la función de carga global:

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

De hecho, me gusta mucho tu pregunta, me hace pensar en diferentes casos de uso.

Para los eventos de JavaScript, realmente desea agregar y eliminar controladores, pero para extenderFunction, ¿cómo podría eliminar la funcionalidad más tarde ? Podría agregar fácilmente un método .revert a funciones extendidas, por init = init.revert()lo que devolvería la función original. Obviamente, esto podría conducir a un código bastante malo, pero quizás le permita hacer algo sin tocar una parte extraña del código base.

Devin G Rhode
fuente