Anular una función de JavaScript al hacer referencia al original


160

Tengo una función, a()que quiero anular, pero también tengo que realizar el original a()en un orden dependiendo del contexto. Por ejemplo, a veces, cuando estoy generando una página, quiero anular así:

function a() {
    new_code();
    original_a();
}

y a veces así:

function a() {
    original_a();
    other_new_code();
}

¿Cómo consigo eso original_a()desde dentro de la anulación a()? ¿Es posible?

Por favor, no sugiera alternativas para anular de esta manera, conozco muchas. Estoy preguntando sobre esta manera específicamente.

Respuestas:


179

Podrías hacer algo como esto:

var a = (function() {
    var original_a = a;

    if (condition) {
        return function() {
            new_code();
            original_a();
        }
    } else {
        return function() {
            original_a();
            other_new_code();
        }
    }
})();

Declarar original_adentro de una función anónima evita que satura el espacio de nombres global, pero está disponible en las funciones internas.

Como Nerdmaster mencionó en los comentarios, asegúrese de incluir el ()al final. Desea llamar a la función externa y almacenar el resultado (una de las dos funciones internas) en a, no almacenar la función externa en sí a.


25
Para cualquier idiota como yo, preste mucha atención al "()" al final, sin eso, devuelve la función externa, no las funciones internas :)
Nerdmaster

@Nerdmaster Gracias por señalar eso. Agregué una nota para ayudar a la gente a notarlo.
Matthew Crumley

55
Wow, intenté hacer esto sin un espacio de nombres y obtuve un desbordamiento de pila, jajaja.
gosukiwi

Creo que el resultado sería el mismo si el primer y último paréntesis se eliminan de la función. Como desde aquí (functi..y }). Hacerlo }();produciría el mismo resultado, ya que (function{})()se utiliza el primer conjunto de paréntesis porque no puede simplemente declarar, anónimo, la expresión de función que tiene que asignarle. Pero al hacer () no estás definiendo nada, solo devolviendo una función.
Muhammad Umer

@MuhammadUmer Tienes razón en que los paréntesis alrededor de la expresión de la función no son necesarios. Los incluí porque sin ellos, si solo miras las primeras dos líneas, parece (al menos para mí) como si estuvieras asignando la función externa directamente a, no llamándola y almacenando el valor de retorno. Los paréntesis me dejan saber que algo más está sucediendo.
Matthew Crumley

88

El patrón Proxy podría ayudarte:

(function() {
    // log all calls to setArray
    var proxied = jQuery.fn.setArray;
    jQuery.fn.setArray = function() {
        console.log( this, arguments );
        return proxied.apply( this, arguments );
    };
})();

Lo anterior envuelve su código en una función para ocultar la variable "proxy". Guarda el método setArray de jQuery en un cierre y lo sobrescribe. Luego, el proxy registra todas las llamadas al método y delega la llamada al original. El uso de apply (esto, argumentos) garantiza que la persona que llama no podrá notar la diferencia entre el método original y el proxy.


10
en efecto. El uso de 'aplicar' y 'argumentos' lo hace mucho más robusto que las otras respuestas
Robert Levy,

Tenga en cuenta que este patrón no requiere jQuery, solo están usando una de las funciones de jQuery como ejemplo.
Kev

28

Gracias a todos, el patrón proxy realmente ayudó ... En realidad, quería llamar a una función global foo ... En ciertas páginas necesito hacer algunas comprobaciones. Entonces hice lo siguiente.

//Saving the original func
var org_foo = window.foo;

//Assigning proxy fucnc
window.foo = function(args){
    //Performing checks
    if(checkCondition(args)){
        //Calling original funcs
        org_foo(args);
    }
};

Gracias, esto realmente me ayudó


3
Al seguir el patrón proxy , será importante en algunos casos implementar este detalle que no está presente en el código de esta Respuesta: en lugar de org_foo(args), llame org_foo.call(this, args). Eso se mantiene thiscomo hubiera sido cuando window.foo llama normalmente (no proxy). Ver esta respuesta
cellepo

10

Puede anular una función usando una construcción como:

function override(f, g) {
    return function() {
        return g(f);
    };
}

Por ejemplo:

 a = override(a, function(original_a) {
      if (condition) { new_code(); original_a(); }
      else { original_a(); other_new_code(); }
 });

Editar: se corrigió un error tipográfico.


+1 ¡Esto es mucho más legible que los otros ejemplos de patrones proxy!
Mu Mind

1
... pero si no me equivoco, esto se limita a las funciones de argumento cero, o al menos a un número predeterminado de argumentos. ¿Hay alguna forma de generalizarlo a números arbitrarios de argumentos sin perder la legibilidad?
Mu Mind

@MuMind: sí, pero usando el patrón proxy correctamente, vea mi respuesta .
Dan Dascalescu

4

Pasar argumentos arbitrarios:

a = override(a, function(original_a) {
    if (condition) { new_code(); original_a.apply(this, arguments) ; }
    else { original_a.apply(this, arguments); other_new_code(); }
});

1
original_aaunque no se refieren argumentos ?
Jonathan.

2

La respuesta que proporciona @Matthew Crumley es hacer uso de las expresiones de función invocadas inmediatamente, para cerrar la función 'a' anterior en el contexto de ejecución de la función devuelta. Creo que esta fue la mejor respuesta, pero personalmente, preferiría pasar la función 'a' como argumento a IIFE. Creo que es más comprensible.

   var a = (function(original_a) {
        if (condition) {
            return function() {
                new_code();
                original_a();
            }
        } else {
            return function() {
                original_a();
                other_new_code();
            }
        }
    })(a);

1

Los ejemplos anteriores no se aplican thiso pasan argumentscorrectamente a la función de anulación. El subrayado _.wrap () ajusta las funciones existentes, aplica thisy pasa argumentscorrectamente. Ver: http://underscorejs.org/#wrap


0

Tenía otro código escrito por otra persona y quería agregar una línea a una función que no pude encontrar en el código. Entonces, como solución, quería anularlo.

Sin embargo, ninguna de las soluciones funcionó para mí.

Esto es lo que funcionó en mi caso:

if (typeof originalFunction === "undefined") {
    originalFunction = targetFunction;
    targetFunction = function(x, y) {
        //Your code
        originalFunction(a, b);
        //Your Code
    };  
}

0

He creado un pequeño ayudante para un escenario similar porque a menudo necesitaba anular funciones de varias bibliotecas. Este asistente acepta un "espacio de nombres" (el contenedor de funciones), el nombre de la función y la función de anulación. Reemplazará la función original en el espacio de nombres referido con la nueva.

La nueva función acepta la función original como primer argumento y los argumentos de las funciones originales como el resto. Preservará el contexto cada vez. También admite funciones nulas y no nulas.

function overrideFunction(namespace, baseFuncName, func) {
    var originalFn = namespace[baseFuncName];
    namespace[baseFuncName] = function () {
        return func.apply(this, [originalFn.bind(this)].concat(Array.prototype.slice.call(arguments, 0)));
    };
}

Uso, por ejemplo, con Bootstrap:

overrideFunction($.fn.popover.Constructor.prototype, 'leave', function(baseFn, obj) {
    // ... do stuff before base call
    baseFn(obj);
    // ... do stuff after base call
});

Sin embargo, no creé ninguna prueba de rendimiento. Posiblemente puede agregar una sobrecarga no deseada que puede o no ser un gran problema, dependiendo de los escenarios.


0

En mi opinión, las respuestas principales no son legibles / mantenibles, y las otras respuestas no vinculan adecuadamente el contexto. Aquí hay una solución legible que usa la sintaxis ES6 para resolver estos dos problemas.

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};

Pero el uso de la sintaxis ES6 hace que su uso sea bastante restringido. ¿Tienes una versión que funcione en ES5?
Eitch

0

Entonces mi respuesta terminó siendo una solución que me permite usar la variable _esta variable apuntando al objeto original. Creo una nueva instancia de un "Cuadrado", pero odiaba la forma en que el "Cuadrado" generaba su tamaño. Pensé que debería seguir mis necesidades específicas. Sin embargo, para hacerlo, necesitaba que el cuadrado tuviera una función "GetSize" actualizada con las funciones internas de esa función que llama a otras funciones que ya existen en el cuadrado, como this.height, this.GetVolume (). Pero para hacerlo, necesitaba hacerlo sin ningún truco loco. Entonces aquí está mi solución.

Alguna otra inicializador de objetos o función auxiliar.

this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(
  this.viewerContainer)
var viewer = this.viewer;
viewer.updateToolbarButtons =  this.updateToolbarButtons(viewer);

Función en el otro objeto.

updateToolbarButtons = function(viewer) {
  var _viewer = viewer;
  return function(width, height){ 
blah blah black sheep I can refer to this.anything();
}
};

0

No estoy seguro de si funcionará en todas las circunstancias, pero en nuestro caso, intentamos anular la describefunción en Jest para poder analizar el nombre y omitir todo el describebloque si cumple con algunos criterios.

Esto es lo que funcionó para nosotros:

function describe( name, callback ) {
  if ( name.includes( "skip" ) )
    return this.describe.skip( name, callback );
  else
    return this.describe( name, callback );
}

Dos cosas que son críticas aquí:

  1. No usamos una función de flecha () =>.

    Las funciones de flecha cambian la referencia a thisy necesitamos que sea el archivo this.

  2. El uso de this.describey en this.describe.skiplugar de just describey describe.skip.

Una vez más, no estoy seguro de que sea valioso para nadie, pero originalmente intentamos escapar con la excelente respuesta de Matthew Crumley, pero necesitábamos hacer que nuestro método funcionara y aceptar parámetros para analizarlos en condicional.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.