JavaScript: klonuj funkcję

115

Jaki jest najszybszy sposób na sklonowanie funkcji w JavaScript (z jej właściwościami lub bez)?

Przychodzą mi do głowy dwie opcje eval(func.toString())i function() { return func.apply(..) }. Martwię się jednak o wydajność eval, a zawijanie pogorszy stos i prawdopodobnie pogorszy wydajność, jeśli zostanie zastosowany dużo lub zastosowany do już zapakowanego.

new Function(args, body) wygląda ładnie, ale jak dokładnie mogę niezawodnie podzielić istniejącą funkcję na argumenty i ciało bez parsera JS w JS?

Z góry dziękuję.

Aktualizacja: Mam na myśli to, że mogę to zrobić

var funcB = funcA.clone(); // where clone() is my extension
funcB.newField = {...};    // without affecting funcA
Andrey Shchekin
źródło
Czy możesz podać przykład pokazujący, co masz na myśli.
JoshBerke
Jasne, dodał. (15 znaków wymagane)
Andrey Shchekin
Nie jestem pewien, ale mogę skopiować = new your_function (); praca?
Savageman
1
Nie sądzę, utworzy instancję używając funkcji jako konstruktora
Andrey Shchekin

Odpowiedzi:

54

Spróbuj tego:

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
źródło
Ok, więc zgłoś się to jedyny sposób? Poprawiłbym to trochę, żeby nie zawijał się dwa razy przy dwukrotnym wywołaniu, ale poza tym ok.
Andrey Shchekin
Apply służy do łatwego przekazywania argumentów. również zadziała to w przypadku, gdy chcesz sklonować konstruktora.
Jared
6
tak, o aplikacji pisałem w oryginalnym poście. Problem polega na tym, że funkcja zawijania, taka jak ta, niszczy jej nazwę i będzie spowalniać po wielu klonach.
Andrey Shchekin
Wydaje się, że jest jeden sposób, aby przynajmniej wpłynąć na właściwość .name, w ten sposób: function fa () {} var fb = function () {fa.apply (this, arguments); }; Object.defineProperties (fb, {nazwa: {wartość: 'fb'}});
Killroy
109

Oto zaktualizowana odpowiedź

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

Jednak .bindjest to nowoczesna (> = iE9) funkcja JavaScript (z obejściem zgodności z MDN )

Uwagi

  1. To nie sklonować obiektem Function dołączone dodatkowe właściwości , łącznie z prototypem nieruchomości. Podziękowania dla @jchook

  2. Nowa funkcja ta zmienna utknęła z argumentem podanym w funkcji bind (), nawet przy nowych wywołaniach funkcji apply (). Kredyt dla @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. Obiekt funkcji powiązany, instanceof traktuje newFunc / oldFunc jako to samo. Podziękowania dla @Christopher
(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc; //gives false however
PicoCreator
źródło
2
Zauważ, że newFuncNIE będzie miał własnego prototypu dla new newFuncinstancji, podczas gdy oldFuncbędzie.
jchook
1
Praktyczny minus: instanceof nie będzie w stanie odróżnić newFunc od oldFunc
Christopher Swasey,
1
@ChristopherSwasey: W rzeczywistości może to być plusem, jeśli chodzi o rozszerzanie funkcjonalności. Ale niestety, będzie to zagmatwane, jeśli nie zostanie dobrze zrozumiane (dodane do odpowiedzi)
PicoCreator
Dużym problemem związanym z tą odpowiedzią jest to, że po związaniu nie można związać się po raz drugi. Kolejne wywołania do zastosowania również ignorują przekazany obiekt „this”. Przykład: var f = function() { console.log('hello ' + this.name) }gdy jest przypisany do {name: 'Bob'}drukowania „cześć Bob”. f.apply({name: 'Sam'})wypisze również „hello Bob”, ignorując obiekt „this”.
Kevin Mooney
1
Jeszcze jeden skrajny przypadek do odnotowania: przynajmniej w V8 (i prawdopodobnie w innych silnikach) zmienia to zachowanie Function.prototype.toString (). Wywołanie .toString () na powiązanej funkcji spowoduje wyświetlenie ciągu podobnego do function () { [native code] }zamiast pełnej zawartości funkcji.
GladstoneKeep
19

Oto nieco lepsza wersja odpowiedzi Jareda. Ten nie będzie miał głęboko zagnieżdżonych funkcji, im więcej klonujesz. Zawsze nazywa oryginał.

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

Ponadto, w odpowiedzi na zaktualizowaną odpowiedź udzieloną przez pico.creator, warto zauważyć, że bind()funkcja dodana w Javascript 1.8.5 ma ten sam problem co odpowiedź Jareda - będzie się zagnieżdżać, powodując coraz wolniejsze funkcje za każdym razem, gdy zostanie użyta.

Justin Warkentin
źródło
w 2019+ prawdopodobnie lepiej jest użyć Symbol () zamiast __properties.
Alexander Mills,
10

Jest ciekawy, ale nadal nie można znaleźć odpowiedzi na temat wydajności powyższe pytanie, napisałem to sens dla nodejs przetestować zarówno wydajność i niezawodność wszystkich prezentowanych (i goli) rozwiązań.

Porównałem czasy tworzenia funkcji klonowania i wykonywania klonów. Wyniki wraz z błędami asercji są zawarte w komentarzu do istoty.

Plus moje dwa grosze (na podstawie sugestii autora):

clone0 cent (szybszy, ale brzydszy):

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

clone4 cent (wolniej, ale dla tych, którzy nie lubią eval () do celów znanych tylko im i ich przodkom):

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

Jeśli chodzi o wydajność, jeśli eval / new Function jest wolniejsze niż rozwiązanie wrapper (i naprawdę zależy to od rozmiaru ciała funkcji), daje ci klon czystej funkcji (i mam na myśli prawdziwy płytki klon z właściwościami, ale stanem niewspółdzielonym) bez niepotrzebnego rozmycia z ukrytymi właściwościami, funkcjami opakowania i problemami ze stosem.

Ponadto zawsze jest jeden ważny czynnik, który należy wziąć pod uwagę: im mniej kodu, tym mniej miejsc na błędy.

Wadą korzystania z funkcji eval / new jest to, że klon i oryginalna funkcja będą działać w różnych zakresach. Nie będzie działać dobrze z funkcjami, które używają zmiennych o określonym zakresie. Rozwiązania wykorzystujące zawijanie podobne do wiązania są niezależne od zakresu.

royaltm
źródło
Uważaj, eval i new Function nie są równoważne. eval działa w zakresie lokalnym, ale funkcja nie. Może to prowadzić do problemów z dostępem do innych zmiennych z wnętrza kodu funkcji. Szczegółowe wyjaśnienie znajduje się na perfectionkills.com/global-eval-what-are-the-options .
Pierre
Dobrze i używając eval lub new Function nie możesz sklonować funkcji razem z jej oryginalnym zakresem.
royaltm
W rzeczywistości: kiedy dodasz Object.assign(newfun.prototype, this.prototype);przed instrukcją return (czysta wersja), Twoja metoda jest najlepszą odpowiedzią.
Vivick,
9

Uruchomienie tej metody było dość ekscytujące, więc tworzy ona klon funkcji przy użyciu wywołania funkcji.

Niektóre ograniczenia dotyczące domknięć opisane w Dokumentacji funkcji 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;
}

Cieszyć się.

Max Dolgov
źródło
5

Krótko i prosto:

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};
micahblu
źródło
1
Używa również wariantu eval pod maską, którego najlepiej unikać z różnych powodów (nie będę się tym zajmował, jest objęty tysiącami innych miejsc).
Andrew Faulkner
2
to rozwiązanie ma swoje miejsce (kiedy klonujesz funkcję użytkownika i nie przejmujesz się, że eval jest używany)
Lloyd
2
To również traci zakres funkcji. Nowa funkcja może odnosić się do zmiennych zakresu zewnętrznego, które nie istnieją już w nowym zakresie.
trusktr
4
const oldFunction = params => {
  // do something
};

const clonedFunction = (...args) => oldFunction(...args);
zhenyulina
źródło
3
const clonedFunction = Object.assign(() => {}, originalFunction);
TraceWright
źródło
Zwróć uwagę, że jest to niekompletne. Spowoduje to skopiowanie właściwości z originalFunction, ale w rzeczywistości nie spowoduje ich wykonania podczas uruchamiania clonedFunction, co jest nieoczekiwane.
David Calhoun
2

Ta odpowiedź jest dla osób, które postrzegają klonowanie funkcji jako odpowiedź na ich pożądane użycie, ale wielu z nich w rzeczywistości nie musi klonować funkcji, ponieważ tak naprawdę chcą mieć możliwość dołączania różnych właściwości do tej samej funkcji, ale tylko zadeklaruj tę funkcję raz.

Zrób to, tworząc funkcję tworzącą funkcję:

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

To nie jest dokładnie to samo, co opisałeś, jednak zależy to od tego, jak chcesz użyć funkcji, którą chcesz sklonować. Zużywa to również więcej pamięci, ponieważ w rzeczywistości tworzy wiele kopii funkcji, raz na wywołanie. Jednak ta technika może rozwiązać przypadek użycia niektórych osób bez potrzeby stosowania skomplikowanej clonefunkcji.

ErikE
źródło
1

Zastanawiam się tylko - dlaczego miałbyś chcieć klonować funkcję, skoro masz prototypy ORAZ możesz ustawić zakres wywołania funkcji na cokolwiek chcesz?

 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);
Wyższy poziom
źródło
1
Jeśli istnieje powód do zmiany pól samej funkcji (samodzielna pamięć podręczna, właściwości „statyczne”), to jest sytuacja, w której chcę sklonować funkcję i zmodyfikować ją bez wpływu na pierwotną.
Andrey Shchekin
Mam na myśli właściwości samej funkcji.
Andrey Shchekin
1
funkcje mogą mieć właściwości, jak każdy obiekt, dlatego
Radu Simionescu,
1

Jeśli chcesz utworzyć klon za pomocą konstruktora Function, powinno działać coś takiego:

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

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

Te klony stracą jednak swoje nazwy i zakres dla wszelkich zamkniętych zmiennych.

tobymackenzie
źródło
1

Poprawiłem odpowiedź Jareda na swój własny sposób:

    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) teraz obsługuje klonowanie konstruktorów (można wywołać z new); w takim przypadku pobiera tylko 10 argumentów (można to zmieniać) - ze względu na niemożność przekazania wszystkich argumentów w oryginalnym konstruktorze

2) wszystko jest prawidłowo zamknięte

max. min
źródło
zamiast tego arguments[0], arguments[1] /*[...]*/dlaczego po prostu nie używasz ...arguments? 1) Nie ma zależności co do ilości argumentów (tutaj ograniczona do 10) 2) krócej
Vivick
Używając operatora rozprzestrzeniania, z pewnością byłaby to moja metoda klonowania OG dla funkcji, dzięki bardzo dużo.
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();

Chociaż nigdy nie polecałbym tego używać, pomyślałem, że byłoby ciekawym małym wyzwaniem wymyślenie bardziej precyzyjnego klonu, biorąc niektóre z praktyk, które wydawały się najlepsze, i trochę je poprawiając. Oto wynik logów:

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

Ta funkcja klonowania:

  1. Zachowuje kontekst.
  2. Jest opakowaniem i uruchamia oryginalną funkcję.
  3. Kopiuje właściwości funkcji.

Zauważ, że ta wersja wykonuje tylko krótką kopię. Jeśli funkcja ma obiekty jako właściwości, to zachowane zostaje odwołanie do oryginalnego obiektu (to samo zachowanie, co obiekt rozkładówka lub obiekt. Oznacza to, że zmiana głębokich właściwości w sklonowanej funkcji wpłynie na obiekt, do którego odwołuje się oryginalna funkcja!

David Calhoun
źródło