JavaScript: clone uma função


115

Qual é a maneira mais rápida de clonar uma função em JavaScript (com ou sem suas propriedades)?

Duas opções que vêm à mente são eval(func.toString())e function() { return func.apply(..) }. Mas estou preocupado com o desempenho de eval e o empacotamento tornará a pilha pior e provavelmente degradará o desempenho se aplicado muito ou aplicado a já empacotado.

new Function(args, body) parece bom, mas como exatamente posso dividir a função existente de forma confiável para args e corpo sem um analisador JS em JS?

Desde já, obrigado.

Atualização: o que quero dizer é ser capaz de fazer

var funcB = funcA.clone(); // where clone() is my extension
funcB.newField = {...};    // without affecting funcA

Você pode dar um exemplo mostrando o que você quer dizer.
— JoshBerke

Claro, adicionado. (15charsrequired)
— Andrey Shchekin

Não tenho certeza, mas poderia copiar = new your_function (); trabalhos?
— Savageman

1
Acho que não, ele vai criar uma instância usando a função como um construtor
— Andrey Shchekin

Respostas:


54

tente isto:

var x = function() {
    return 1;
};

var t = function(a,b,c) {
    return a+b+c;
};


Function.prototype.clone = function() {
    var that = this;
    var temp = function temporary() { return that.apply(this, arguments); };
    for(var key in this) {
        if (this.hasOwnProperty(key)) {
            temp[key] = this[key];
        }
    }
    return temp;
};

alert(x === x.clone());
alert(x() === x.clone()());

alert(t === t.clone());
alert(t(1,1,1) === t.clone()(1,1,1));
alert(t.clone()(1,1,1));

Ok, então aplicar é a única maneira? Eu melhoraria isso um pouco para que não embrulhe duas vezes quando chamado duas vezes, mas caso contrário, ok.
— Andrey Shchekin

aplicar é usado para passar os argumentos facilmente. também, isso funcionará para instâncias em que você deseja clonar um construtor.
— Jared

6
sim, eu escrevi sobre aplicar no post original. O problema é que uma função de agrupamento como essa destrói seu nome e ficará mais lenta após muitos clones.
— Andrey Shchekin

Parece haver uma maneira de pelo menos afetar a propriedade .name assim: function fa () {} var fb = function () {fa.apply (this, arguments); }; Object.defineProperties (fb, {nome: {valor: 'fb'}});
— Killroy

109

Aqui está uma resposta atualizada

var newFunc = oldFunc.bind({}); //clones the function with '{}' acting as it's new 'this' parameter

No entanto, ".bind" é um recurso moderno (> = iE9) do JavaScript (com uma solução alternativa de compatibilidade do MDN)

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

Observação: ele não clona as propriedades anexadas adicionais do objeto de função , incluindo a propriedade prototype . Crédito para @jchook

Nota: que a nova função desta variável está presa ao argumento dado em bind (), mesmo em novas chamadas de função apply (). Crédito para @Kevin

function oldFunc() { console.log(this.msg); }
var newFunc = oldFunc.bind( { msg:"You shall not pass!" } ); // this object is binded
newFunc.apply( { msg:"hello world" } ); //logs "You shall not pass!" instead

Nota: objeto de função vinculada, instanceof trata newFunc / oldFunc como iguais. Crédito para @Christopher

(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc;                 //gives false however

2
Observe que newFuncNÃO terá seu próprio protótipo para new newFuncinstâncias, mas oldFuncsim.
— jchook

1
Desvantagem prática: instanceof não será capaz de distinguir entre newFunc e oldFunc
— Christopher Swasey

1
@ChristopherSwasey: Também pode ser uma vantagem ao estender as funcionalidades. Mas, infelizmente, será confuso se não for bem compreendido (adicionado à resposta)
— PicoCreator

Um grande problema com essa resposta é que, depois de vincular, você não pode vincular uma segunda vez. As chamadas subsequentes para aplicar também ignoram o objeto 'this' transmitido. Exemplo: var f = function() { console.log('hello ' + this.name) }quando vinculado a {name: 'Bob'}imprime 'hello Bob'. f.apply({name: 'Sam'})também imprimirá 'hello Bob', ignorando o objeto 'this'.
— Kevin Mooney

1
Um outro caso extremo a ser observado: pelo menos no V8 (e possivelmente em outros motores), isso muda o comportamento de Function.prototype.toString (). Chamar .toString () na função associada fornecerá a você uma string em function () { [native code] }vez do conteúdo completo da função.
— GladstoneKeep

19

Aqui está uma versão um pouco melhor da resposta de Jared. Quanto mais você clonar, este não terminará com funções profundamente aninhadas. Sempre chama o original.

Function.prototype.clone = function() {
    var cloneObj = this;
    if(this.__isClone) {
      cloneObj = this.__clonedFrom;
    }

    var temp = function() { return cloneObj.apply(this, arguments); };
    for(var key in this) {
        temp[key] = this[key];
    }

    temp.__isClone = true;
    temp.__clonedFrom = cloneObj;

    return temp;
};

Além disso, em resposta à resposta atualizada fornecida por pico.creator, é importante notar que a bind()função adicionada em Javascript 1.8.5 tem o mesmo problema que a resposta de Jared - ela continuará aninhando causando funções cada vez mais lentas cada vez que é usada.


em 2019+, provavelmente melhor usar Symbol () em vez de __properties.
— Alexander Mills

10

Sendo curioso, mas ainda incapaz de encontrar a resposta para o tópico de desempenho da pergunta acima, escrevi esta essência para nodejs para testar o desempenho e a confiabilidade de todas as soluções apresentadas (e pontuadas).

Eu comparei os tempos de parede da criação de uma função de clone e a execução de um clone. Os resultados, juntamente com os erros de asserção, estão incluídos no comentário da essência.

Mais meus dois centavos (com base na sugestão do autor):

clone0 cent (mais rápido, mas mais feio):

Function.prototype.clone = function() {
  var newfun;
  eval('newfun=' + this.toString());
  for (var key in this)
    newfun[key] = this[key];
  return newfun;
};

clone4 cent (mais lento, mas para aqueles que não gostam de eval () para fins que só eles e seus ancestrais conhecem):

Function.prototype.clone = function() {
  var newfun = new Function('return ' + this.toString())();
  for (var key in this)
    newfun[key] = this[key];
  return newfun;
};

Quanto ao desempenho, se eval / new Function for mais lento do que a solução do wrapper (e realmente depende do tamanho do corpo da função), ele fornece um clone de função simples (e eu quero dizer o clone superficial verdadeiro com propriedades, mas estado não compartilhado) sem fuzz desnecessário com propriedades ocultas, funções de invólucro e problemas com pilha.

Além disso, há sempre um fator importante que você precisa levar em consideração: quanto menos código, menos espaços para erros.

A desvantagem de usar a função eval / new é que o clone e a função original irão operar em escopos diferentes. Não funcionará bem com funções que usam variáveis ​​de escopo. As soluções que usam encapsulamento tipo bind são independentes do escopo.


Esteja ciente de que eval e new Function não são equivalentes. eval opera no escopo local, mas Function não. Isso pode levar a problemas de acesso a outras variáveis ​​de dentro do código da função. Consulte perfectionkills.com/global-eval-what-are-the-options para obter uma explicação detalhada.
— Pierre

Certo e usando eval ou new Function, você não pode clonar a função junto com seu escopo original.
— royaltm

Na verdade: depois de adicionar Object.assign(newfun.prototype, this.prototype);antes da instrução return (versão limpa), seu método é a melhor resposta.
— Vivick

9

Foi muito emocionante fazer esse método funcionar, então ele faz um clone de uma função usando a chamada de Function.

Algumas limitações sobre fechamentos descritos em Referência de função MDN

function cloneFunc( func ) {
  var reFn = /^function\s*([^\s(]*)\s*\(([^)]*)\)[^{]*\{([^]*)\}$/gi
    , s = func.toString().replace(/^\s|\s$/g, '')
    , m = reFn.exec(s);
  if (!m || !m.length) return; 
  var conf = {
      name : m[1] || '',
      args : m[2].replace(/\s+/g,'').split(','),
      body : m[3] || ''
  }
  var clone = Function.prototype.constructor.apply(this, [].concat(conf.args, conf.body));
  return clone;
}

Aproveitar.


5

Curto e simples:

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};

1
Além disso, ele usa uma variante de eval sob o capô, que é melhor evitada por uma variedade de razões (não vou entrar nisso aqui, ela é coberta em milhares de outros lugares).
— Andrew Faulkner

2
esta solução tem seu lugar (quando você está clonando uma função de usuário e não se importa se eval é usado)
— Lloyd

2
Isso também perde o escopo da função. A nova função pode se referir a vars de escopo externo que não existem mais no novo escopo.
— trusktr

4
const oldFunction = params => {
  // do something
};

const clonedFunction = (...args) => oldFunction(...args);

3
const clonedFunction = Object.assign(() => {}, originalFunction);

Observe que isso está incompleto. Isso copiará as propriedades de originalFunction, mas não executará realmente quando você executar clonedFunction, o que é inesperado.
— David Calhoun

2

Esta resposta é para pessoas que veem a clonagem de uma função como a resposta para seu uso desejado, mas que muitos não precisam realmente clonar uma função, porque o que eles realmente querem é simplesmente poder anexar propriedades diferentes à mesma função, mas apenas declare essa função uma vez.

Faça isso criando uma função de criação de função:

function createFunction(param1, param2) {
   function doSomething() {
      console.log('in the function!');
   }
   // Assign properties to `doSomething` if desired, perhaps based
   // on the arguments passed into `param1` and `param2`. Or,
   // even return a different function from among a group of them.
   return doSomething;
};

let a = createFunction();
a.something = 1;
let b = createFunction();
b.something = 2; // does not overwrite a.something
console.log(a.something);
a();
b();

Isso não é exatamente o mesmo que você descreveu, no entanto, depende de como você deseja usar a função que deseja clonar. Isso também usa mais memória porque, na verdade, cria várias cópias da função, uma vez por invocação. No entanto, essa técnica pode resolver o caso de uso de algumas pessoas sem a necessidade de uma clonefunção complicada .


1

Apenas me perguntando - por que você desejaria clonar uma função quando você tem protótipos E pode definir o escopo de uma chamada de função para qualquer coisa que desejar?

 var funcA = {};
 funcA.data = 'something';
 funcA.changeData = function(d){ this.data = d; }

 var funcB = {};
 funcB.data = 'else';

 funcA.changeData.call(funcB.data);

 alert(funcA.data + ' ' + funcB.data);

1
Se houver uma razão para alterar os campos da própria função (cache autocontido, propriedades 'estáticas'), então há uma situação em que desejo clonar uma função e modificá-la sem afetar a original.
— Andrey Shchekin

Refiro-me às propriedades da própria função.
— Andrey Shchekin

1
funções podem ter propriedades, como qualquer objeto, é por isso
— Radu Simionescu

1

Se você deseja criar um clone usando o construtor Function, algo como isto deve funcionar:

_cloneFunction = function(_function){
    var _arguments, _body, _result;
    var _regexFunction = /^function[\s]+[\w]*\(([\w\s,_\$]*)?\)\{(.*)\}$/;
    var _regexArguments = /((?!=^|,)([\w\$_]))+/g;
    var _matches = _function.toString().match(_regexFunction)
    if(_matches){
        if(_matches[1]){
            _result = _matches[1].match(_regexArguments);
        }else{
            _result = [];
        }
        _result.push(_matches[2]);
    }else{
        _result = [];
    }
    var _clone = Function.apply(Function, _result);
    // if you want to add attached properties
    for(var _key in _function){
        _clone[_key] = _function[_key];
    }
    return _clone;
}

Um teste simples:

(function(){
    var _clone, _functions, _key, _subKey;
    _functions = [
        function(){ return 'anonymous function'; }
        ,function Foo(){ return 'named function'; }
        ,function Bar(){ var a = function(){ return 'function with internal function declaration'; }; return a; }
        ,function Biz(a,boo,c){ return 'function with parameters'; }
    ];
    _functions[0].a = 'a';
    _functions[0].b = 'b';
    _functions[1].b = 'b';
    for(_key in _functions){
        _clone = window._cloneFunction(_functions[_key]);
        console.log(_clone.toString(), _clone);
        console.log('keys:');
        for(_subKey in _clone){
            console.log('\t', _subKey, ': ', _clone[_subKey]);
        }
    }
})()

No entanto, esses clones perderão seus nomes e escopo para quaisquer variáveis ​​fechadas.


1

Impulsionei a resposta de Jared da minha própria maneira:

    Function.prototype.clone = function() {
        var that = this;
        function newThat() {
            return (new that(
                arguments[0],
                arguments[1],
                arguments[2],
                arguments[3],
                arguments[4],
                arguments[5],
                arguments[6],
                arguments[7],
                arguments[8],
                arguments[9]
            ));
        }
        function __clone__() {
            if (this instanceof __clone__) {
                return newThat.apply(null, arguments);
            }
            return that.apply(this, arguments);
        }
        for(var key in this ) {
            if (this.hasOwnProperty(key)) {
                __clone__[key] = this[key];
            }
        }
        return __clone__;
    };

1) agora ele suporta clonagem de construtores (pode chamar com novos); nesse caso leva apenas 10 argumentos (você pode variar) - devido à impossibilidade de passar todos os argumentos no construtor original

2) tudo está em fechamentos corretos


em vez de arguments[0], arguments[1] /*[...]*/por que você simplesmente não usa ...arguments? 1) Não há dependência em relação à quantidade de argumentos (aqui limitados a 10) 2) mais curtos
— Vivick

Com o uso do operador spread, esse seria definitivamente meu método de clonagem OG para funções, muito obrigado.
— Vivick

0
function cloneFunction(Func, ...args) {
  function newThat(...args2) {
    return new Func(...args2);
  }
  function clone() {
    if (this instanceof clone) {
      return newThat(...args);
    }
    return Func.apply(this, args);
  }
  for (const key in Func) {
    if (Func.hasOwnProperty(key)) {
      clone[key] = Func[key];
    }
  }
  Object.defineProperty(clone, 'name', { value: Func.name, configurable: true })
  return clone
};

function myFunction() {
  console.log('Called Function')
}

myFunction.value = 'something';

const newFunction = cloneFunction(myFunction);

newFunction.another = 'somethingelse';

console.log('Equal? ', newFunction === myFunction);
console.log('Names: ', myFunction.name, newFunction.name);
console.log(myFunction);
console.log(newFunction);
console.log('InstanceOf? ', newFunction instanceof myFunction);

myFunction();
newFunction();

Embora eu nunca recomende usar isso, achei que seria um pequeno desafio interessante chegar a um clone mais preciso pegando algumas das práticas que pareciam ser as melhores e corrigindo-as um pouco. Aqui está o resultado dos logs:

Equal?  false
Names:  myFunction myFunction
{ [Function: myFunction] value: 'something' }
{ [Function: myFunction] value: 'something', another: 'somethingelse' }
InstanceOf?  false
Called Function
Called Function

0
const clone = (fn, context = this) => {
  // Creates a new function, optionally preserving desired context.
  const newFn = fn.bind(context);

  // Shallow copies over function properties, if any.
  return Object.assign(newFn, fn);
}

// Usage:

// Setup the function to copy from.
const log = (...args) => console.log(...args);
log.testProperty = 1;

// Clone and make sure the function and properties are intact.
const log2 = clone(log);
log2('foo');
// -> 'foo'
log2.testProperty;
// -> 1

// Make sure tweaks to the clone function's properties don't affect the original function properties.
log2.testProperty = 2;
log2.testProperty;
// -> 2
log.testProperty;
// -> 1

Esta função clone:

  1. Preserva o contexto.
  2. É um wrapper e executa a função original.
  3. Cópias das propriedades da função.

Observe que esta versão executa apenas uma cópia superficial. Se sua função tiver objetos como propriedades, a referência ao objeto original é preservada (mesmo comportamento de Propagação de objeto ou Object.assign). Isso significa que alterar as propriedades profundas na função clonada afetará o objeto referenciado na função original!

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.