JavaScript: clona una funzione


115

Qual è il modo più veloce per clonare una funzione in JavaScript (con o senza le sue proprietà)?

Due opzioni che mi vengono in mente sono eval(func.toString())e function() { return func.apply(..) }. Ma sono preoccupato per le prestazioni di eval e il wrapping peggiorerà lo stack e probabilmente ridurrà le prestazioni se applicato molto o applicato a già avvolto.

new Function(args, body) sembra carino, ma come posso dividere esattamente la funzione esistente in modo affidabile in args e body senza un parser JS in JS?

Grazie in anticipo.

Aggiornamento: quello che voglio dire è essere in grado di fare

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

Puoi fare un esempio che mostri cosa intendi.
JoshBerke

Certo, aggiunto. (15 caratteri richiesti)
Andrey Shchekin,

Non sono sicuro, ma potrei copiare = new your_function (); lavoro?
Savageman

1
Non credo, creerà un'istanza utilizzando la funzione come costruttore
Andrey Shchekin

Risposte:


54

prova questo:

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, quindi applicare è l'unico modo? Migliorerei un po 'in modo che non si avvolga due volte quando viene chiamato due volte, ma per il resto, ok.
Andrey Shchekin

si usa per passare facilmente gli argomenti. inoltre, questo funzionerà per le istanze in cui si desidera clonare un costruttore.
Jared

6
si, ho scritto di candidarti nel post originale. il problema è che la funzione di wrapping come questa distrugge il suo nome e rallenterà dopo molti cloni.
Andrey Shchekin

Sembra esserci un modo per influenzare almeno la proprietà .name in questo modo: function fa () {} var fb = function () {fa.apply (this, arguments); }; Object.defineProperties (fb, {nome: {valore: 'fb'}});
Killroy

109

Ecco una risposta aggiornata

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

Tuttavia .bindè una funzionalità moderna (> = iE9) di JavaScript (con una soluzione alternativa di compatibilità da MDN )

Appunti

  1. Non clona le proprietà aggiuntive associate dell'oggetto funzione , incluso il prototipo proprietà . Ringraziamo @jchook

  2. La nuova funzione di questa variabile è bloccata con l'argomento fornito su bind (), anche su chiamate alla nuova funzione apply (). Ringraziamo @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
  1. Oggetto funzione associato, instanceof considera newFunc / oldFunc come lo stesso. Ringraziamo @Christopher
(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc; //gives false however

2
Nota che newFuncNON avrà il suo prototipo per le new newFuncistanze, mentre lo oldFuncfarà.
jchook

1
Svantaggio pratico: instanceof non sarà in grado di distinguere tra newFunc e oldFunc
Christopher Swasey

1
@ChristopherSwasey: Può effettivamente essere anche un vantaggio, quando si estendono le funzionalità. Ma ahimè, sarà
fonte di

Un grosso problema con questa risposta è che una volta eseguito il binding, non puoi eseguire il binding una seconda volta. Anche le chiamate successive ad apply ignorano l'oggetto "this" passato. Esempio: var f = function() { console.log('hello ' + this.name) }quando associato a {name: 'Bob'}stampa "ciao Bob". f.apply({name: 'Sam'})stamperà anche "ciao Bob", ignorando l'oggetto "questo".
Kevin Mooney

1
Un altro caso limite da notare: almeno in V8 (e possibilmente altri motori), questo cambia il comportamento di Function.prototype.toString (). Chiamare .toString () sulla funzione associata ti darà una stringa simile function () { [native code] }invece del contenuto completo della funzione.
Gladstone

19

Ecco una versione leggermente migliore della risposta di Jared. Questo non finirà con funzioni profondamente nidificate più cloni. Chiama sempre l'originale.

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

Inoltre, in risposta alla risposta aggiornata fornita da pico.creator, vale la pena notare che la bind()funzione aggiunta in Javascript 1.8.5 ha lo stesso problema della risposta di Jared: manterrà l'annidamento causando funzioni sempre più lente ogni volta che viene utilizzata.


nel 2019+, probabilmente è meglio usare Symbol () invece di __properties.
Alexander Mills

10

Essere curiosi, ma ancora in grado di trovare la risposta al tema prestazioni della domanda di cui sopra, ho scritto questo succo per nodejs alla prova sia le prestazioni e l'affidabilità di tutte le soluzioni presentate (e ha ottenuto).

Ho confrontato i tempi del muro della creazione di una funzione clone e l'esecuzione di un clone. I risultati insieme agli errori di asserzione sono inclusi nel commento dell'essenza.

Più i miei due centesimi (in base al suggerimento dell'autore):

clone0 cent (più veloce ma più brutto):

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

clone4 cent (più lento ma per coloro a cui non piace eval () per scopi noti solo a loro e ai loro antenati):

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

Per quanto riguarda le prestazioni, se la funzione eval / new è più lenta della soluzione wrapper (e dipende davvero dalla dimensione del corpo della funzione), ti dà il clone della funzione nuda (e intendo il vero clone superficiale con proprietà ma stato non condiviso) senza fuzz non necessario con proprietà nascoste, funzioni wrapper e problemi con lo stack.

Inoltre c'è sempre un fattore importante da prendere in considerazione: meno codice, meno posti per errori.

Lo svantaggio dell'utilizzo della funzione eval / new è che il clone e la funzione originale funzioneranno in ambiti diversi. Non funzionerà bene con le funzioni che utilizzano variabili con ambito. Le soluzioni che utilizzano il wrapping di tipo bind sono indipendenti dall'ambito.


Attenzione che eval e new Function non sono equivalenti. eval opera in ambito locale, ma Function no. Ciò potrebbe portare a problemi di collegamento per accedere ad altre variabili dall'interno del codice funzione. Vedi perfectionkills.com/global-eval-what-are-the-options per una spiegazione esauriente.
Pierre

A destra e utilizzando eval o new Function non è possibile clonare la funzione insieme al suo ambito originale.
royaltm

È un dato di fatto: una volta aggiunto Object.assign(newfun.prototype, this.prototype);prima dell'istruzione return (versione pulita), il tuo metodo è la risposta migliore.
Vivick

9

È stato piuttosto eccitante far funzionare questo metodo, quindi crea un clone di una funzione usando la chiamata di funzione.

Alcune limitazioni sulle chiusure descritte in MDN Function Reference

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

Godere.


5

Breve e semplice:

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

1
Inoltre, utilizza una variante di eval sotto il cofano, che è meglio evitare per una serie di motivi (non entrerò in questo qui, è coperto in migliaia di altri posti).
Andrew Faulkner

2
questa soluzione ha il suo posto (quando stai clonando una funzione utente e non ti interessa che venga utilizzato eval)
Lloyd

2
Ciò perde anche l'ambito della funzione. La nuova funzione può fare riferimento a variabili dell'ambito esterno che non esistono più nel nuovo ambito.
trusktr

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

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

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

Nota che questo è incompleto. Questo copierà le proprietà da originalFunction, ma non lo eseguirà effettivamente quando esegui clonedFunction, il che è inaspettato.
David Calhoun

2

Questa risposta è per le persone che vedono la clonazione di una funzione come la risposta al loro utilizzo desiderato, ma che molti non in realtà bisogno di clonare una funzione, perché ciò che vogliono veramente è semplicemente essere in grado di allegare proprietà diverse alla stessa funzione, ma solo dichiarare quella funzione una volta.

Fallo creando una funzione di creazione di funzioni:

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

Questo non è esattamente lo stesso che hai delineato, tuttavia, dipende da come desideri utilizzare la funzione che desideri clonare. Questo utilizza anche più memoria perché in realtà crea più copie della funzione, una volta per invocazione. Tuttavia, questa tecnica può risolvere il caso d'uso di alcune persone senza la necessità di una clonefunzione complicata .


1

Mi chiedo solo: perché dovresti clonare una funzione quando hai prototipi E puoi impostare l'ambito di una chiamata di funzione su qualsiasi cosa desideri?

 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 c'è un motivo per cambiare i campi della funzione stessa (cache autonoma, proprietà 'statiche'), allora ci sono situazioni in cui voglio clonare una funzione e modificarla senza influenzare quella originale.
Andrey Shchekin

Intendo le proprietà della funzione stessa.
Andrey Shchekin

1
le funzioni possono avere proprietà, come qualsiasi oggetto, ecco perché
Radu Simionescu

1

Se vuoi creare un clone usando il costruttore di funzioni, dovrebbe funzionare qualcosa di simile:

_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;
}

Un semplice test:

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

Tuttavia, questi cloni perderanno i loro nomi e l'ambito per qualsiasi variabile chiusa.


1

Ho imposto la risposta di Jared a modo mio:

    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) ora supporta la clonazione dei costruttori (può chiamare con new); in tal caso richiede solo 10 argomenti (puoi variare) - a causa dell'impossibilità di passare tutti gli argomenti nel costruttore originale

2) tutto è nelle chiusure corrette


invece di arguments[0], arguments[1] /*[...]*/perché non usi semplicemente ...arguments? 1) Non c'è dipendenza per quanto riguarda la quantità di argomenti (qui limitati a 10) 2) più brevi
Vivick

Con l'uso dell'operatore spread, questo sarebbe sicuramente il mio metodo di clonazione OG per le funzioni, grazie mille.
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();

Anche se non consiglierei mai di usarlo, ho pensato che sarebbe stata una piccola sfida interessante trovare un clone più preciso prendendo alcune delle pratiche che sembravano essere le migliori e aggiustandole un po '. Ecco il risultato dei log:

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

Questa funzione clone:

  1. Preserva il contesto.
  2. È un wrapper ed esegue la funzione originale.
  3. Copia sulle proprietà della funzione.

Nota che questa versione esegue solo una copia superficiale. Se la funzione ha oggetti come proprietà, viene mantenuto il riferimento all'oggetto originale (stesso comportamento di Object spread o Object.assign). Ciò significa che la modifica delle proprietà profonde nella funzione clonata influenzerà l'oggetto a cui si fa riferimento nella funzione originale!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.