JavaScript: Klonen Sie eine Funktion

115

Was ist der schnellste Weg, um eine Funktion in JavaScript (mit oder ohne ihre Eigenschaften) zu klonen?

Zwei Optionen, die mir in den Sinn kommen, sind eval(func.toString())und function() { return func.apply(..) }. Aber ich mache mir Sorgen um die Leistung von Eval und Wrapping, was den Stack verschlechtert und wahrscheinlich die Leistung beeinträchtigt, wenn es häufig angewendet wird oder auf bereits verpackte Produkte angewendet wird.

new Function(args, body) sieht gut aus, aber wie genau kann ich vorhandene Funktionen ohne JS-Parser in JS zuverlässig in Argumente und Körper aufteilen?

Danke im Voraus.

Update: Was ich meine, ist in der Lage zu sein

var funcB = funcA.clone(); // where clone() is my extension
funcB.newField = {...};    // without affecting funcA
Andrey Shchekin
quelle
Können Sie ein Beispiel geben, das zeigt, was Sie meinen?
JoshBerke
Sicher, hinzugefügt. (15charsrequired)
Andrey Shchekin
Ich bin mir nicht sicher, könnte aber kopieren = new your_function (); Arbeit?
Savageman
1
Ich denke nicht, es wird eine Instanz mit Funktion als Konstruktor erstellen
Andrey Shchekin

Antworten:

54

Versuche dies:

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));
Jared
quelle
Ok, also bewerben ist der einzige Weg? Ich würde das ein bisschen verbessern, damit es nicht zweimal umbrochen wird, wenn es zweimal aufgerufen wird, aber ansonsten ok.
Andrey Shchekin
apply wird verwendet, um die Argumente einfach zu übergeben. Dies funktioniert auch in Fällen, in denen Sie einen Konstruktor klonen möchten.
Jared
6
ja, ich habe im ursprünglichen Beitrag über bewerben geschrieben. Das Problem ist, dass eine solche Wrapping-Funktion ihren Namen zerstört und nach vielen Klonen langsamer wird.
Andrey Shchekin
Es scheint eine Möglichkeit zu geben, zumindest die .name-Eigenschaft wie folgt zu beeinflussen: function fa () {} var fb = function () {fa.apply (this, Argumente); }; Object.defineProperties (fb, {name: {value: 'fb'}});
Killroy
109

Hier ist eine aktualisierte Antwort

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

".Bind" ist jedoch eine moderne (> = iE9) Funktion von JavaScript (mit einer Kompatibilitätsumgehung von MDN).

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

Hinweis: dass es nicht der Fall ist klonen das Funktionsobjekt zusätzlich angebaute Eigenschaften , einschließlich der Prototyp - Eigenschaft. Gutschrift an @jchook

Hinweis: Bei der neuen Funktion dieser Variablen bleibt das Argument bei bind () erhalten, auch bei Aufrufen neuer Funktion apply (). Gutschrift an @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

Hinweis: Gebundenes Funktionsobjekt, Instanz behandelt newFunc / oldFunc als gleich. Gutschrift an @Christopher

(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc;                 //gives false however
PicoCreator
quelle
2
Beachten Sie, dass newFuncfür new newFuncInstanzen KEIN eigener Prototyp vorhanden oldFuncist.
Jchook
1
Praktischer Nachteil: Instanz von kann nicht zwischen newFunc und oldFunc unterscheiden
Christopher Swasey
1
@ChristopherSwasey: Es kann auch ein Vorteil sein, wenn Funktionen erweitert werden. Aber leider wird es verwirrend sein, wenn es nicht gut verstanden wird (zur Antwort hinzugefügt)
PicoCreator
Ein großes Problem bei dieser Antwort ist, dass Sie nach dem Binden kein zweites Mal binden können. Nachfolgende Aufrufe zum Anwenden ignorieren auch das übergebene 'this'-Objekt. Beispiel: var f = function() { console.log('hello ' + this.name) }Wenn gebunden an {name: 'Bob'}"Hallo Bob" gedruckt wird. f.apply({name: 'Sam'})wird auch 'Hallo Bob' drucken und das 'this'-Objekt ignorieren.
Kevin Mooney
1
Ein weiterer Randfall: Zumindest in V8 (und möglicherweise in anderen Engines) ändert dies das Verhalten von Function.prototype.toString (). Wenn Sie .toString () für die gebundene Funktion aufrufen, erhalten Sie eine Zeichenfolge wie function () { [native code] }anstelle des Inhalts der vollständigen Funktion.
GladstoneKeep
19

Hier ist eine etwas bessere Version von Jareds Antwort. Dieser wird nicht mit tief verschachtelten Funktionen enden, je mehr Sie klonen. Es nennt immer das 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;
};

Als Antwort auf die aktualisierte Antwort von pico.creator ist anzumerken, dass die bind()in Javascript 1.8.5 hinzugefügte Funktion das gleiche Problem hat wie die Antwort von Jared - sie verschachtelt weiter und verursacht bei jeder Verwendung immer langsamere Funktionen.

Justin Warkentin
quelle
ab 2019 ist es wahrscheinlich besser, Symbol () anstelle von __properties zu verwenden.
Alexander Mills
10

Da ich neugierig war, aber immer noch keine Antwort auf das Leistungsthema der obigen Frage finden konnte, schrieb ich diesen Kern für nodejs, um sowohl die Leistung als auch die Zuverlässigkeit aller vorgestellten (und bewerteten) Lösungen zu testen.

Ich habe die Wandzeiten einer Klonfunktionserstellung und die Ausführung eines Klons verglichen. Die Ergebnisse zusammen mit Assertionsfehlern sind im Kernkommentar enthalten.

Plus meine zwei Cent (basierend auf dem Vorschlag des Autors):

clone0 cent (schneller aber hässlicher):

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

clone4 cent (langsamer, aber für diejenigen, die eval () nicht für Zwecke mögen, die nur ihnen und ihren Vorfahren bekannt sind):

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

Wenn eval / new Function langsamer als die Wrapper-Lösung ist (und dies hängt wirklich von der Größe des Funktionskörpers ab), erhalten Sie einen nackten Funktionsklon (und ich meine den echten flachen Klon mit Eigenschaften, aber nicht gemeinsam genutztem Zustand) ohne unnötigen Fuzz mit versteckten Eigenschaften, Wrapper-Funktionen und Problemen mit dem Stack.

Außerdem gibt es immer einen wichtigen Faktor, den Sie berücksichtigen müssen: Je weniger Code, desto weniger Fehlerstellen.

Der Nachteil der Verwendung der eval / new-Funktion besteht darin, dass der Klon und die ursprüngliche Funktion in unterschiedlichen Bereichen ausgeführt werden. Es funktioniert nicht gut mit Funktionen, die Gültigkeitsbereichsvariablen verwenden. Die Lösungen, die bind-like Wrapping verwenden, sind unabhängig vom Bereich.

Royaltm
quelle
Beachten Sie, dass Bewertung und neue Funktion nicht gleichwertig sind. eval opers on local scope, Function jedoch nicht. Dies kann zu Problemen beim Zugriff auf andere Variablen innerhalb des Funktionscodes führen. Siehe perfectionkills.com/global-eval-what-are-the-options für eine ausführliche Erklärung.
Pierre
Richtig und wenn Sie entweder eval oder new Function verwenden, können Sie die Funktion nicht zusammen mit ihrem ursprünglichen Bereich klonen.
Royaltm
In der Tat: Sobald Sie Object.assign(newfun.prototype, this.prototype);vor der return-Anweisung (saubere Version) hinzufügen , ist Ihre Methode die beste Antwort.
Vivick
9

Es war ziemlich aufregend, diese Methode zum Laufen zu bringen, sodass mit dem Funktionsaufruf ein Klon einer Funktion erstellt wird.

Einige Einschränkungen bezüglich der in der MDN-Funktionsreferenz beschriebenen Verschlüsse

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

Genießen.

Max Dolgov
quelle
5

Kurz und einfach:

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};
Micahblu
quelle
1
Außerdem wird eine Variante von eval unter der Haube verwendet, die aus verschiedenen Gründen am besten vermieden wird (darauf wird hier nicht eingegangen, es wird an Tausenden anderer Stellen behandelt).
Andrew Faulkner
2
Diese Lösung hat ihren Platz (wenn Sie eine Benutzerfunktion klonen und sich nicht darum kümmern, dass eval verwendet wird)
Lloyd
2
Dies verliert auch den Funktionsumfang. Die neue Funktion kann sich auf äußere Bereichsvariablen beziehen, die im neuen Bereich nicht mehr vorhanden sind.
trusktr
4
const oldFunction = params => {
  // do something
};

const clonedFunction = (...args) => oldFunction(...args);
Zhenyulin
quelle
3
const clonedFunction = Object.assign(() => {}, originalFunction);
TraceWright
quelle
Beachten Sie, dass dies unvollständig ist. Dadurch werden die Eigenschaften kopiert originalFunction, aber beim Ausführen nicht ausgeführt clonedFunction, was unerwartet ist.
David Calhoun
2

Diese Antwort ist für Leute , die als Antwort auf ihre gewünschte Nutzung, eine Funktion zu sehen Klonen aber , die viele nicht wirklich braucht , um eine Funktion zu klonen, weil das, was sie wirklich wollen , ist einfach in der Lage sein , verschiedene Eigenschaften auf die gleiche Funktion zu befestigen, sondern nur Deklarieren Sie diese Funktion einmal.

Erstellen Sie dazu eine funktionserzeugende Funktion:

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

Dies ist nicht genau das Gleiche wie Sie beschrieben haben. Es hängt jedoch davon ab, wie Sie die Funktion verwenden möchten, die Sie klonen möchten. Dies verbraucht auch mehr Speicher, da tatsächlich mehrere Kopien der Funktion einmal pro Aufruf erstellt werden. Diese Technik kann jedoch den Anwendungsfall einiger Leute lösen, ohne dass eine komplizierte cloneFunktion erforderlich ist.

ErikE
quelle
1

Ich frage mich nur, warum Sie eine Funktion klonen möchten, wenn Sie Prototypen haben UND den Umfang eines Funktionsaufrufs auf alles einstellen können, was Sie möchten.

 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);
Upperstage
quelle
1
Wenn es einen Grund gibt, Funktionsfelder selbst zu ändern (in sich geschlossener Cache, 'statische' Eigenschaften), gibt es Situationen, in denen ich eine Funktion klonen und ändern möchte, ohne die ursprüngliche zu beeinflussen.
Andrey Shchekin
Ich meine die Eigenschaften der Funktion selbst.
Andrey Shchekin
1
Funktionen können Eigenschaften haben, wie jedes Objekt, deshalb
Radu Simionescu
1

Wenn Sie einen Klon mit dem Funktionskonstruktor erstellen möchten, sollte Folgendes funktionieren:

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

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

Diese Klone verlieren jedoch ihren Namen und ihren Gültigkeitsbereich für alle geschlossenen Variablen.

Tobymackenzie
quelle
1

Ich habe Jareds Antwort auf meine eigene Weise bestätigt:

    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) jetzt unterstützt es das Klonen von Konstruktoren (kann mit new aufrufen); In diesem Fall werden nur 10 Argumente verwendet (Sie können es variieren), da nicht alle Argumente im ursprünglichen Konstruktor übergeben werden können

2) Alles ist in korrekten Verschlüssen

max.minin
quelle
anstatt arguments[0], arguments[1] /*[...]*/warum benutzt du nicht einfach ...arguments? 1) Es gibt keine Abhängigkeit bezüglich der Anzahl der Argumente (hier auf 10 begrenzt) 2) kürzer
Vivick
Mit der Verwendung des Spread-Operators wäre dies definitiv meine OG-Klonierungsmethode für Funktionen, vielen Dank.
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();

Obwohl ich niemals empfehlen würde, dies zu verwenden, dachte ich, es wäre eine interessante kleine Herausforderung, einen präziseren Klon zu entwickeln, indem ich einige der Praktiken, die am besten zu sein schienen, ein wenig korrigiere. Hier ist das Ergebnis der Protokolle:

Equal?  false
Names:  myFunction myFunction
{ [Function: myFunction] value: 'something' }
{ [Function: myFunction] value: 'something', another: 'somethingelse' }
InstanceOf?  false
Called Function
Called Function
Braden Rockwell Napier
quelle
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

Diese Klonfunktion:

  1. Bewahrt den Kontext.
  2. Ist ein Wrapper und führt die ursprüngliche Funktion aus.
  3. Kopiert über Funktionseigenschaften.

Beachten Sie, dass diese Version nur eine flache Kopie ausführt. Wenn Ihre Funktion Objekte als Eigenschaften hat, bleibt der Verweis auf das ursprüngliche Objekt erhalten (dasselbe Verhalten wie Object Spread oder Object.assign). Dies bedeutet, dass sich das Ändern der Tiefeneigenschaften in der geklonten Funktion auf das Objekt auswirkt, auf das in der ursprünglichen Funktion verwiesen wird!

David Calhoun
quelle