Agregar código a una función de JavaScript mediante programación

114

Estoy intentando personalizar una biblioteca JS existente sin modificar el código JS original. Este código se carga en algunos archivos JS externos a los que tengo acceso, y lo que me gustaría hacer es cambiar una de las funciones contenidas en el archivo original sin copiar y pegar todo en el segundo archivo JS.
Entonces, por ejemplo, el JS fuera de los límites podría tener una función como esta:

var someFunction = function(){
    alert("done");
}

Me gustaría poder agregar o anteponer algún código JS en esa función de alguna manera. La razón es principalmente que en el JS intocable original la función es bastante enorme y si ese JS alguna vez se actualiza, la función con la que lo sobrescribo estará desactualizada.

No estoy del todo seguro de que esto sea posible, pero pensé que lo comprobaría.

Munzilla
fuente
3
la primera respuesta de esto debería ayudarlo
DarkAjax
¿Quieres algo como una función de devolución de llamada?
sinemetu1

Respuestas:

219

Si someFunctionestá disponible globalmente, puede almacenar en caché la función, crear la suya propia y hacer que la suya la llame.

Entonces, si este es el original ...

someFunction = function() {
    alert("done");
}

Harías esto ...

someFunction = (function() {
    var cached_function = someFunction;

    return function() {
        // your code

        var result = cached_function.apply(this, arguments); // use .apply() to call it

        // more of your code

        return result;
    };
})();

Aquí está el violín


Tenga en cuenta que utilizo .applypara llamar a la función en caché. Esto me permite retener el valor esperado de thisy pasar los argumentos que se pasaron como argumentos individuales, independientemente de cuántos haya.

usuario1106925
fuente
10
Esta respuesta debería ser realmente la mejor: conserva la funcionalidad de la función en cuestión ... +1 de mí.
Reid
17
+1 por usar apply: esta es la única respuesta que realmente resuelve el problema.
solitario
2
@minitech: ¿Qué ... no estás familiarizado con la funcitonpalabra clave de JavaScript ? ;) Gracias por la edición
1
@gdoron: Acabo de agregar un comentario. A menos que esté realmente confundido en este momento, no conozco ningún navegador que no acepte un objeto Arguments real como segundo argumento .apply(). Pero si fuera un objeto similar a una matriz como un objeto jQuery, por ejemplo, entonces sí, algunos navegadores arrojarían un error
2
Esta respuesta, como sucede, resuelve el problema de usar objetos instanciados para controlar videos individuales de YouTube incrustados a través de la API Iframe de YouTube. No tiene idea de cuánto tiempo he estado tratando de evitar el hecho de que la API requiere que su devolución de llamada sea una función global única cuando intento crear una clase para manejar los datos de cada video de una manera modular única. Eres mi héroe del día.
Carnix
31

primero almacene la función real en una variable.

var oldFunction = someFunction;

luego define el tuyo:

someFunction = function(){
  // do something before
  oldFunction();
  // do something after
};
driangle
fuente
9
Si su función es un método que necesitará usar applypara llamarlo. Vea la respuesta de am.
hugomg
Se trabajó para mí, pero necesitaba reemplazar someFunctioncon window.someFunctionel código de seguridad. La razón es que mi función se declaró dentro de un $(document).ready()controlador jquery .
Nelson
10

Puede crear una función que llame a su código y luego llame a la función.

var old_someFunction = someFunction;
someFunction = function(){
    alert('Hello');
    old_someFunction();
    alert('Goodbye');
}
Cohete Hazmat
fuente
6

No sé si puede actualizar la función, pero dependiendo de cómo se haga referencia, puede crear una nueva función en su lugar:

var the_old_function = someFunction;
someFunction = function () {
    /* ..My new code... */
    the_old_function();
    /* ..More of my new code.. */
}
Ned Batchelder
fuente
5

También. Si desea cambiar el contexto local, debe volver a crear la función. Por ejemplo:

var t = function() {
    var a = 1;
};

var z = function() {
    console.log(a);
};

Ahora

z() // => log: undefined

Luego

var ts = t.toString(),
    zs = z.toString();

ts = ts.slice(ts.indexOf("{") + 1, ts.lastIndexOf("}"));
zs = zs.slice(zs.indexOf("{") + 1, zs.lastIndexOf("}"));

var z = new Function(ts + "\n" + zs);

Y

z() // => log: 1

Pero este es solo el ejemplo más simple. Todavía se necesitará mucho trabajo para manejar argumentos, comentarios y valor de retorno. Además, todavía existen muchos escollos.
toString | rebanada | indexOf | lastIndexOf | nueva función

Ivan Negro
fuente
1

El patrón de proxy (utilizado por el usuario1106925) se puede poner dentro de una función. El que escribí a continuación funciona en funciones que no están en el alcance global, e incluso en prototipos. Lo usarías así:

extender(
  objectContainingFunction,
  nameOfFunctionToExtend,
  parameterlessFunctionOfCodeToPrepend,
  parameterlessFunctionOfCodeToAppend
)

En el fragmento de abajo, puedes verme usando la función para extender test.prototype.doIt ().

// allows you to prepend or append code to an existing function
function extender (container, funcName, prepend, append) {

    (function() {

        let proxied = container[funcName];

        container[funcName] = function() {
            if (prepend) prepend.apply( this );
            let result = proxied.apply( this, arguments );
            if (append) append.apply( this );
            return result;
        };

    })();

}

// class we're going to want to test our extender on
class test {
    constructor() {
        this.x = 'instance val';
    }
    doIt (message) {
        console.log(`logged: ${message}`);
        return `returned: ${message}`;
    }
}

// extends test.prototype.doIt()
// (you could also just extend the instance below if desired)
extender(
    test.prototype, 
    'doIt', 
    function () { console.log(`prepended: ${this.x}`) },
    function () { console.log(`appended: ${this.x}`) }
);

// See if the prepended and appended code runs
let tval = new test().doIt('log this');
console.log(tval);

pwilcox
fuente