JavaScript: bir işlevi klonlayın


115

JavaScript'te bir işlevi klonlamanın en hızlı yolu nedir (özellikleri olsun ya da olmasın)?

Akla gelen iki seçenek eval(func.toString())ve function() { return func.apply(..) }. Ancak değerlendirme ve paketlemenin performansı konusunda endişeliyim ve yığını daha da kötüleştirecek ve çok fazla uygulanırsa veya halihazırda sarılmış olarak uygulanırsa muhtemelen performansı düşürecektir.

new Function(args, body) güzel görünüyor, ancak JS'de JS ayrıştırıcısı olmadan mevcut işlevi bağımsız değişkenlere ve gövdeye tam olarak nasıl bölebilirim?

Şimdiden teşekkürler.

Güncelleme: Demek istediğim, yapabilmek

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

Ne demek istediğini gösteren bir örnek verebilir misin?
JoshBerke

Elbette eklendi. (15 karakter gerekli)
Andrey

Emin değilim, ancak kopyalayabilir = new your_function (); iş?
Savageman

1
Sanmıyorum, yapıcı olarak işlevi kullanarak bir örnek oluşturacak
Andrey Shchekin

Yanıtlar:


54

bunu dene:

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

Tamam, bu yüzden başvurmak tek yol mu? Bunu biraz geliştirirdim, böylece iki kez çağrıldığında iki kez sarmaz, aksi halde tamam.
Andrey Shchekin

uygula argümanları kolayca iletmek için kullanılır. ayrıca bu, bir oluşturucuyu klonlamak istediğiniz örnekler için de işe yarar.
Jared

6
evet, orijinal gönderide başvuru hakkında yazdım. sorun, bunun gibi sarma işlevinin adını yok etmesi ve birçok klondan sonra yavaşlamasıdır.
Andrey Shchekin

.Name özelliğini en azından şu şekilde etkilemenin bir yolu var gibi görünüyor: function fa () {} var fb = function () {fa.apply (this, arguments); }; Object.defineProperties (fb, {ad: {değer: 'fb'}});
Killroy

109

İşte güncellenmiş bir cevap

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

Ancak .bindJavaScript'in modern (> = iE9) bir özelliğidir (MDN'den bir uyumluluk geçici çözümü ile )

notlar

  1. Bu klonlamak gelmez fonksiyon nesne ek ekli özellikleri , dahil prototip özelliği. @Jchook'a Kredi

  2. Yeni işlev Bu değişken yeni function () çağrıları uygulamak bile, bağlama (verilen argüman) ile yapıştırılır. @ Kevin'e Kredi

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. Bağlı işlev nesnesi, instanceof, newFunc / oldFunc'ı aynı şekilde ele alır. @Christopher'a Kredi
(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc; //gives false however

2
newFuncWill new newFunciken örnekler için kendi prototipine sahip OLMAYACAĞINIZI unutmayın oldFunc.
jchook

1
Pratik dezavantaj: instanceof, newFunc ile oldFunc arasında ayrım yapamayacak
Christopher Swasey

1
@ChristopherSwasey: İşlevleri genişletirken aslında bir tersi de olabilir. Ama ne yazık ki, iyi anlaşılmazsa kafa karıştırıcı olacaktır (cevaba eklenir)
PicoCreator

Bu cevapla ilgili büyük bir sorun, bir kez bağladığınızda, ikinci kez bağlanamamanızdır. Sonraki uygulama çağrıları da iletilen 'bu' nesnesini yok sayar. Örnek: var f = function() { console.log('hello ' + this.name) }ciltlendiğinde {name: 'Bob'}'merhaba Bob' yazar. f.apply({name: 'Sam'})ayrıca 'bu' nesnesini yok sayarak 'merhaba Bob' yazacaktır.
Kevin Mooney

1
Dikkat edilmesi gereken bir diğer uç durum: En azından V8'de (ve muhtemelen diğer motorlarda) bu, Function.prototype.toString () davranışını değiştirir. Bound işlevinde .toString () işlevinin çağrılması, size function () { [native code] }tam işlevin içeriği yerine bir dizge verecektir .
GladstoneKeep

19

İşte Jared'in cevabının biraz daha iyi bir versiyonu. Bu, ne kadar çok klonlarsanız, derinlemesine iç içe geçmiş işlevlerle sona ermeyecek. Her zaman orijinali çağırır.

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

Ayrıca, pico.creator tarafından verilen güncellenmiş yanıta yanıt olarak, Javascript 1.8.5'e bind()eklenen işlevin Jared'in cevabıyla aynı soruna sahip olduğunu belirtmekte fayda var - her kullanıldığında daha yavaş ve daha yavaş işlevlere neden olacak şekilde yuvalanmaya devam edecektir.


2019+ sürümlerinde __properties yerine Symbol () kullanmak muhtemelen daha iyidir.
Alexander Mills

10

Merak ettiğim halde yukarıdaki sorunun performans konusuna hala cevap bulamadığımdan, bu özü nodejs için sunulan (ve puanlanan) tüm çözümlerin hem performansını hem de güvenilirliğini test etmek için yazdım.

Bir klon işlevinin oluşturulmasıyla bir klonun çalıştırılmasının duvar zamanlarını karşılaştırdım. İddia hatalarıyla birlikte sonuçlar, esasın yorumuna dahil edilir.

Artı benim iki sentim (yazarın önerisine göre):

clone0 cent (daha hızlı ama daha çirkin):

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

clone4 cent (daha yavaş, ancak eval () 'dan hoşlanmayanlar için yalnızca kendileri ve ataları tarafından bilinen amaçlar için):

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

Performansa gelince, eval / new Function, sarmalayıcı çözümünden daha yavaşsa (ve gerçekten işlev gövdesi boyutuna bağlıysa), size çıplak işlev klonu verir (ve özellikleri olan ancak paylaşılmamış durumdaki gerçek sığ klonu) gereksiz tüyler olmadan gizli özellikler, sarmalayıcı işlevleri ve yığınla ilgili sorunlar.

Ayrıca, her zaman dikkate almanız gereken önemli bir faktör vardır: daha az kod, daha az hata yeri.

Eval / new İşlevini kullanmanın dezavantajı, klonun ve orijinal işlevin farklı kapsamlarda çalışacak olmasıdır. Kapsamlı değişkenler kullanan işlevlerle iyi çalışmaz. Bağlama benzeri sarma kullanan çözümler kapsamdan bağımsızdır.


Eval ve new Function'ın eşdeğer olmadığına dikkat edin. yerel kapsam üzerinde işlem yapar, ancak İşlev yapmaz. Bu, işlev kodunun içinden diğer değişkenlere erişmeye bağlı sorunlara yol açabilir. Kapsamlı bir açıklama için perfectionkills.com/global-eval-what-are-the-options sayfasına bakın .
Pierre

Doğru ve eval veya new Function'ı kullanarak işlevi orijinal kapsamıyla birlikte klonlayamazsınız.
royaltm

Nitekim: Object.assign(newfun.prototype, this.prototype);return ifadesinden (temiz versiyon) önce eklediğinizde , yönteminiz en iyi cevaptır.
Vivick

9

Bu yöntemin çalışması oldukça heyecan vericiydi, bu yüzden Function çağrısını kullanarak bir fonksiyonun klonunu yapıyor.

MDN İşlev Referansında açıklanan kapatmalarla ilgili bazı sınırlamalar

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

Zevk almak.


5

Kısa ve basit:

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

1
Ayrıca, çeşitli nedenlerle en iyi şekilde kaçınılması gereken bir başlık altında bir değerlendirme varyantı kullanır (burada bu konuya girmeyecektir, 1000'lerce başka yerde kapsanmaktadır).
Andrew Faulkner

2
Bu çözüm (eğer kullanıcı işlevini Klonladığınız ve bu eval umurumda değil ne zaman kullanılır) kendi yeri vardır
Lloyd

2
Bu aynı zamanda işlev kapsamını da kaybeder. Yeni işlev, artık yeni kapsamda bulunmayan dış kapsam değişkenlerine başvurabilir.
trusktr

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

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

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

Bunun eksik olduğuna dikkat edin. Bu, özellikleri içinden kopyalar originalFunction, ancak çalıştırdığınızda aslında çalıştırmaz clonedFunctionki bu beklenmedik bir durumdur.
David Calhoun

2

Bu cevap, bir işlevi klonlamayı, istenen kullanımlarının yanıtı olarak gören, ancak birçoğunun aslında bir işlevi klonlamaya ihtiyacı olmayan insanlar içindir , çünkü gerçekten istedikleri, aynı işleve farklı özellikler ekleyebilmektir, ancak yalnızca bu işlevi bir kez beyan edin.

Bunu, işlev oluşturan bir işlev oluşturarak yapın:

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

Bu, belirttiğinizle tam olarak aynı değildir, ancak klonlamak istediğiniz işlevi nasıl kullanmak istediğinize bağlıdır. Bu ayrıca daha fazla bellek kullanır çünkü aslında çağrı başına bir kez işlevin birden çok kopyasını oluşturur. Ancak bu teknik, bazı kişilerin kullanım durumunu karmaşık bir cloneişleve ihtiyaç duymadan çözebilir .


1

Merak ediyorum - prototipleriniz varken neden bir işlevi klonlamak isteyesiniz VE bir işlev çağrısının kapsamını istediğiniz herhangi bir şeye ayarlayabilirsiniz?

 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
İşlev alanlarını (kendi kendine yeten önbellek, 'statik' özellikler) değiştirmek için bir neden varsa, bir işlevi klonlamak ve orijinal olanı etkilemeden onu değiştirmek istediğim durumlar vardır.
Andrey Shchekin

Fonksiyonun kendisinin özelliklerini kastediyorum.
Andrey Shchekin

1
işlevler herhangi bir nesne gibi özelliklere sahip olabilir, bu yüzden
Radu Simionescu

1

Function yapıcısını kullanarak bir klon oluşturmak istiyorsanız, bunun gibi bir şey çalışmalıdır:

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

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

Bu klonlar, kapalı değişkenler için adlarını ve kapsamlarını kaybedeceklerdir.


1

Jared'in cevabını kendi tarzıma uydurdum:

    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) artık yapıcıların klonlanmasını destekliyor (yenileriyle çağırabilir); bu durumda yalnızca 10 argüman alır (bunu değiştirebilirsiniz) - tüm argümanları orijinal kurucuda geçirmenin imkansızlığı nedeniyle

2) her şey doğru kapanışta


arguments[0], arguments[1] /*[...]*/neden yerine kullanmıyorsun ...arguments? 1) Argüman miktarına bağlılık yok (burada 10 ile sınırlı) 2) daha kısa
Vivick

Yayılma operatörünün kullanılmasıyla, bu kesinlikle işlevler için OG klonlama yöntemim olurdu, çok teşekkürler.
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();

Bunu kullanmayı asla önermesem de, en iyi gibi görünen uygulamalardan bazılarını alıp biraz düzelterek daha hassas bir klon bulmanın ilginç ve küçük bir zorluk olacağını düşündüm. İşte günlüklerin sonucu:

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

Bu klon işlevi:

  1. Bağlamı korur.
  2. Bir sarmalayıcıdır ve orijinal işlevi çalıştırır.
  3. Fonksiyon özelliklerinin üzerine kopyalar.

Not Bu sürümü sadece basit bir kopyasını gerçekleştirir. İşlevinizin özellik olarak nesneler varsa, orijinal nesneye referans korunur (Nesne yayılması veya Object.assign ile aynı davranış). Bu, klonlanmış işlevdeki derin özelliklerin değiştirilmesinin, orijinal işlevde atıfta bulunulan nesneyi etkileyeceği anlamına gelir!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.