Substituindo uma função JavaScript ao fazer referência à original


160

Tenho uma função a()que desejo substituir, mas também o original deve a()ser executado em uma ordem, dependendo do contexto. Por exemplo, às vezes, quando estou gerando uma página, quero substituir dessa forma:

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

e às vezes assim:

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

Como faço para obter isso original_a()de dentro do controle a()? Isso é possível?

Por favor, não sugira alternativas para ultrapassar dessa maneira, eu sei de muitas. Estou perguntando sobre esse caminho especificamente.

Respostas:


179

Você poderia fazer algo assim:

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 uma função anônima evita que você atrapalhe o espaço de nomes global, mas está disponível nas funções internas.

Como o Nerdmaster mencionado nos comentários, inclua o ()no final. Você deseja chamar a função externa e armazenar o resultado (uma das duas funções internas) a, não armazenar a própria função externa a.


25
Para quaisquer idiotas lá fora, como eu - muita atenção pagar para o "()" no final - sem isso, ele retorna a função externa, e não as funções internas :)
Nerdmaster

@ Nerdmaster Obrigado por apontar isso. Adicionei uma nota para ajudar as pessoas a perceberem.
Matthew Crumley

5
Uau, tentei fazer isso sem um espaço para nome e fiquei com um estouro de pilha, lol.
gosukiwi

Eu acho que o resultado seria o mesmo se o primeiro e o último parênteses forem removidos da função. Como daqui (functi..e }). Fazer }();isso produziria o mesmo resultado, pois o (function{})()primeiro conjunto de parênteses é usado porque você não pode simplesmente declarar, anônima, a expressão da função que precisa atribuir. Mas ao fazer () você não está definindo nada, apenas retornando uma função.
Muhammad Umer

@MuhammadUmer Você está certo que os parênteses em torno da expressão da função não são necessários. Eu os incluí porque, sem eles, se você apenas observar as primeiras linhas, parece (pelo menos para mim) que você está atribuindo a função externa diretamente a, sem chamá-la e armazenando o valor de retorno. Os parênteses me avisam que há algo mais acontecendo.
Matthew Crumley

88

O padrão Proxy pode ajudá-lo a:

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

O acima envolve seu código em uma função para ocultar a variável "proxy". Ele salva o método setArray do jQuery em um fechamento e o substitui. O proxy registra todas as chamadas para o método e delega a chamada para o original. O uso de apply (this, argument) garante que o chamador não será capaz de perceber a diferença entre o método original e o proxy.


10
de fato. o uso de 'Aplicar' e 'argumentos' torna este muito mais robusto do que as outras respostas
Robert Levy

Observe que esse padrão não requer jQuery - eles estão apenas usando uma das funções do jQuery como exemplo.
Kev

28

Obrigado pessoal, o padrão de proxy realmente ajudou ..... Na verdade, eu queria chamar uma função global foo. Em determinadas páginas, preciso fazer algumas verificações. Então eu fiz o seguinte.

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

Thnx isso realmente me ajudou


3
Ao seguir o padrão de proxy , em alguns casos, será importante implementar esse detalhe que não está presente no código deste Respondente: Em vez de org_foo(args), chame org_foo.call(this, args). Isso mantém thiscomo teria sido quando o window.foo chama normalmente (sem proxy). Veja esta resposta
cellepo 20/02

10

Você pode substituir uma função usando uma construção como:

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

Por exemplo:

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

Editar: corrigido um erro de digitação.


+1 Isso é muito mais legível do que os outros exemplos de padrões de proxy!
Mu Mente

1
... mas se não me engano, isso é limitado a funções de argumento zero, ou a um número predeterminado de argumentos pelo menos. Existe alguma maneira de generalizá-lo para números arbitrários de argumentos sem perder a legibilidade?
Mu Mind

@ MuMind: sim, mas usando o padrão de proxy corretamente - veja minha resposta .
Dan Dascalescu 22/03

4

Passando argumentos arbitrários:

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
argumentos não se referem original_aembora?
Jonathan.

2

A resposta que @Matthew Crumley fornece é usar as expressões de função invocadas imediatamente, para fechar a função 'a' mais antiga no contexto de execução da função retornada. Eu acho que essa foi a melhor resposta, mas pessoalmente, eu preferiria passar a função 'a' como argumento para o IIFE. Eu acho que é mais compreensível.

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

1

Os exemplos acima não se aplicam thisou passam argumentscorretamente para a substituição da função. Sublinhado _.wrap () agrupa as funções existentes, aplica thise passa argumentscorretamente. Veja: http://underscorejs.org/#wrap


0

Eu tinha algum código escrito por outra pessoa e queria adicionar uma linha a uma função que não consegui encontrar no código. Então, como solução alternativa, eu queria substituí-lo.

Nenhuma das soluções funcionou para mim.

Aqui está o que funcionou no meu caso:

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

0

Eu criei um pequeno auxiliar para um cenário semelhante, porque muitas vezes precisava substituir funções de várias bibliotecas. Esse auxiliar aceita um "espaço para nome" (o contêiner da função), o nome da função e a função de substituição. Ele substituirá a função original no espaço para nome referido pelo novo.

A nova função aceita a função original como o primeiro argumento e os argumentos das funções originais como o restante. Ele preservará o contexto sempre. Ele também suporta funções nulas e não 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 exemplo, com o Bootstrap:

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

Eu não criei nenhum teste de desempenho. Possivelmente, pode adicionar uma sobrecarga indesejada que pode ou não ser um grande problema, dependendo dos cenários.


0

Na minha opinião, as principais respostas não são legíveis / mantidas e as outras respostas não vinculam adequadamente o contexto. Aqui está uma solução legível usando a sintaxe ES6 para resolver esses dois problemas.

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

Mas o uso da sintaxe do ES6 o torna bastante restrito em uso. Você tem uma versão que funcione no ES5?
eitch

0

Portanto, minha resposta acabou sendo uma solução que me permite usar a variável _está apontando para o objeto original. Eu crio uma nova instância de um "Quadrado", no entanto, odiava a maneira como o "Quadrado" gerava seu tamanho. Eu pensei que deveria seguir minhas necessidades específicas. No entanto, para fazer isso, eu precisava que o quadrado tivesse uma função "GetSize" atualizada com os internos dessa função chamando outras funções já existentes no quadrado, como this.height, this.GetVolume (). Mas, para fazer isso, eu precisava fazer isso sem nenhum truque maluco. Então aqui está a minha solução.

Alguma outra função inicializadora de objeto ou auxiliar.

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

Função no outro objeto.

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

0

Não tenho certeza se funcionará em todas as circunstâncias, mas, no nosso caso, estávamos tentando substituir a describefunção no Jest para poder analisar o nome e pular tododescribe bloco se ele atender a alguns critérios.

Aqui está o que funcionou para nós:

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

Duas coisas que são críticas aqui:

  1. Nós não usamos uma função de seta () => .

    As funções de seta alteram a referência thise precisamos que seja o arquivo this.

  2. O uso de this.describee em this.describe.skipvez de apenas describee describe.skip.

Novamente, não tenho certeza se é de valor para alguém, mas originalmente tentamos nos safar da excelente resposta de Matthew Crumley, mas precisávamos tornar nosso método uma função e aceitar parâmetros para analisá-los na condicional.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.