JavaScript: mengkloning fungsi

115

Apa cara tercepat untuk mengkloning fungsi di JavaScript (dengan atau tanpa propertinya)?

Dua opsi yang muncul di benak adalah eval(func.toString())dan function() { return func.apply(..) }. Tapi saya khawatir kinerja eval dan wrapping akan memperburuk stack dan mungkin akan menurunkan performa jika diterapkan banyak atau diterapkan pada yang sudah dibungkus.

new Function(args, body) terlihat bagus, tapi bagaimana tepatnya saya bisa membagi fungsi yang ada menjadi args dan body tanpa parser JS di JS?

Terima kasih sebelumnya.

Pembaruan: Yang saya maksud adalah bisa melakukan

var funcB = funcA.clone(); // where clone() is my extension
funcB.newField = {...};    // without affecting funcA
Andrey Shchekin
sumber
Bisakah Anda memberi contoh yang menunjukkan apa yang Anda maksud.
JoshBerke
Oke, ditambahkan. (15charsrequired)
Andrey Shchekin
Saya tidak yakin, tapi bisa copy = new your_function (); kerja?
Savageman
1
Saya rasa tidak, itu akan membuat instance menggunakan fungsi sebagai konstruktor
Andrey Shchekin

Jawaban:

54

coba ini:

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
sumber
Ok, jadi melamar adalah satu-satunya cara? Saya akan memperbaiki ini sedikit sehingga tidak membungkus dua kali ketika dipanggil dua kali, tetapi sebaliknya, ok.
Andrey Shchekin
apply digunakan untuk menyampaikan argumen dengan mudah. juga, ini akan berfungsi untuk contoh di mana Anda ingin mengkloning konstruktor.
Jared
6
ya, saya menulis tentang melamar di posting asli. Masalahnya, fungsi pembungkus seperti ini merusak namanya dan akan melambat setelah banyak klon.
Andrey Shchekin
Tampaknya ada satu cara untuk setidaknya mempengaruhi properti .name seperti ini: function fa () {} var fb = function () {fa.apply (this, arguments); }; Object.defineProperties (fb, {name: {value: 'fb'}});
Killroy
109

Berikut adalah jawaban yang diperbarui

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

Namun .bindadalah fitur JavaScript modern (> = iE9) (dengan solusi kompatibilitas dari MDN )

Catatan

  1. Ini tidak mengkloning fungsi objek tambahan yang melekat sifat , termasuk para prototipe properti. Penghargaan untuk @jchook

  2. Fungsi baru ini variabel terjebak dengan argumen yang diberikan pada bind (), bahkan pada fungsi baru berlaku () panggilan. Penghargaan untuk @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. Objek fungsi terikat, instanceof memperlakukan newFunc / oldFunc sebagai sama. Penghargaan untuk @Christopher
(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc; //gives false however
PicoCreator
sumber
2
Perhatikan bahwa newFuncTIDAK akan memiliki prototipe sendiri untuk new newFuncinstance, sementara oldFuncakan.
jchook
1
Kelemahan praktis: instanceof tidak dapat membedakan antara newFunc dan oldFunc
Christopher Swasey
1
@ChristopherSwasey: Ini sebenarnya bisa menjadi terbalik juga, ketika memperluas fungsionalitas. Namun sayang, akan membingungkan jika tidak dipahami dengan baik (ditambahkan ke jawaban)
PicoCreator
Masalah besar dengan jawaban ini adalah sekali Anda mengikat, Anda tidak dapat mengikat untuk kedua kalinya. Panggilan selanjutnya untuk diterapkan juga mengabaikan objek 'ini' yang diteruskan. Contoh: var f = function() { console.log('hello ' + this.name) }ketika terikat untuk {name: 'Bob'}mencetak 'halo Bob'. f.apply({name: 'Sam'})juga akan mencetak 'halo Bob', mengabaikan objek 'ini'.
Kevin Mooney
1
Satu kasus tepi lain yang perlu diperhatikan: Setidaknya di V8 (dan mungkin mesin lain), ini mengubah perilaku Function.prototype.toString (). Memanggil .toString () pada fungsi terikat akan memberi Anda string seperti function () { [native code] }alih - alih konten fungsi penuh.
Gladstone
19

Ini adalah versi jawaban Jared yang sedikit lebih baik. Yang ini tidak akan berakhir dengan fungsi yang sangat bertingkat semakin Anda mengkloning. Itu selalu menyebut aslinya.

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

Juga, sebagai tanggapan atas jawaban terbaru yang diberikan oleh pico.creator, perlu dicatat bahwa bind()fungsi yang ditambahkan dalam Javascript 1.8.5 memiliki masalah yang sama dengan jawaban Jared - ini akan terus bersarang menyebabkan fungsi yang lebih lambat dan lambat setiap kali digunakan.

Justin Warkentin
sumber
di 2019+, mungkin lebih baik menggunakan Symbol () daripada __properties.
Alexander Mills
10

Karena penasaran tetapi masih tidak dapat menemukan jawaban untuk topik kinerja dari pertanyaan di atas, saya menulis inti dari nodejs ini untuk menguji kinerja dan keandalan semua solusi yang disajikan (dan diberi skor).

Saya telah membandingkan waktu dinding pembuatan fungsi klon dan eksekusi klon. Hasil bersama dengan kesalahan pernyataan disertakan dalam komentar inti.

Ditambah dua sen saya (berdasarkan saran penulis):

clone0 sen (lebih cepat tapi lebih jelek):

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

clone4 cent (lebih lambat tetapi bagi mereka yang tidak menyukai eval () untuk tujuan yang hanya diketahui oleh mereka dan leluhur mereka):

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

Adapun kinerjanya, jika eval / Fungsi baru lebih lambat dari solusi pembungkus (dan itu benar-benar tergantung pada ukuran tubuh fungsi), itu memberi Anda klon fungsi telanjang (dan maksud saya klon dangkal sejati dengan properti tetapi status tidak dibagikan) tanpa fuzz yang tidak perlu dengan properti tersembunyi, fungsi pembungkus, dan masalah dengan tumpukan.

Selain itu, selalu ada satu faktor penting yang perlu Anda pertimbangkan: semakin sedikit kode, semakin sedikit tempat untuk kesalahan.

Kelemahan dari penggunaan fungsi eval / baru adalah bahwa fungsi klon dan asli akan beroperasi dalam cakupan yang berbeda. Ini tidak akan berfungsi dengan baik dengan fungsi yang menggunakan variabel cakupan. Solusi yang menggunakan pembungkusan seperti ikatan tidak bergantung pada cakupan.

royaltm
sumber
Berhati-hatilah karena eval dan Fungsi baru tidak setara. eval beroperasi pada cakupan lokal, tetapi Fungsi tidak. Hal ini dapat menyebabkan masalah yang mengikat untuk mengakses variabel lain dari dalam kode fungsi. Lihat perfectionkills.com/global-eval-what-are-the-options untuk penjelasan lengkap.
Pierre
Benar dan dengan menggunakan eval atau Fungsi baru, Anda tidak dapat menggandakan fungsi bersama dengan cakupan aslinya.
royaltm
Faktanya: setelah Anda menambahkan Object.assign(newfun.prototype, this.prototype);sebelum pernyataan return (versi bersih), metode Anda adalah jawaban terbaik.
Vivick
9

Cukup mengasyikkan untuk membuat metode ini berfungsi, jadi metode ini membuat klon dari suatu fungsi menggunakan panggilan Fungsi.

Beberapa batasan tentang closure dijelaskan di Referensi Fungsi 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;
}

Nikmati.

Max Dolgov
sumber
5

Singkat dan sederhana:

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};
micahblu.dll
sumber
1
Selain itu, ia menggunakan varian eval di bawah tenda, yang sebaiknya dihindari karena berbagai alasan (tidak akan dibahas di sini, ini dibahas di 1000 tempat lain).
Andrew Faulkner
2
solusi ini ada tempatnya (saat Anda mengkloning fungsi pengguna dan tidak peduli bahwa eval digunakan)
Lloyd
2
Ini juga kehilangan ruang lingkup fungsi. Fungsi baru mungkin merujuk ke vars lingkup luar yang tidak lagi ada di lingkup baru.
trusktr
4
const oldFunction = params => {
  // do something
};

const clonedFunction = (...args) => oldFunction(...args);
zhenyulin
sumber
3
const clonedFunction = Object.assign(() => {}, originalFunction);
TraceWright
sumber
Perhatikan bahwa ini tidak lengkap. Ini akan menyalin properti dari originalFunction, tetapi tidak akan benar-benar mengeksekusinya ketika Anda menjalankan clonedFunction, yang tidak terduga.
David Calhoun
2

Jawaban ini untuk orang-orang yang melihat penggandaan suatu fungsi sebagai jawaban untuk penggunaan yang diinginkan, tetapi banyak yang sebenarnya tidak perlu mengkloning suatu fungsi, karena yang sebenarnya mereka inginkan hanyalah dapat melampirkan properti yang berbeda ke fungsi yang sama, tetapi hanya nyatakan fungsi itu satu kali.

Lakukan ini dengan membuat fungsi pembuatan fungsi:

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

Ini tidak persis sama seperti yang Anda uraikan, namun, ini tergantung pada bagaimana Anda ingin menggunakan fungsi yang ingin Anda klon. Ini juga menggunakan lebih banyak memori karena sebenarnya membuat beberapa salinan fungsi, satu kali per pemanggilan. Namun, teknik ini dapat menyelesaikan kasus penggunaan beberapa orang tanpa memerlukan clonefungsi yang rumit .

ErikE
sumber
1

Hanya ingin tahu - mengapa Anda ingin mengkloning suatu fungsi saat Anda memiliki prototipe DAN dapat menyetel cakupan panggilan fungsi ke apa pun yang Anda inginkan?

 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
sumber
1
Jika ada alasan untuk mengubah bidang fungsi itu sendiri (cache mandiri, properti 'statis'), maka ada situasi ketika saya ingin mengkloning fungsi dan memodifikasinya tanpa mempengaruhi yang asli.
Andrey Shchekin
Maksud saya sifat dari fungsi itu sendiri.
Andrey Shchekin
1
fungsi dapat memiliki properti, seperti objek apa pun, itulah sebabnya
Radu Simionescu
1

Jika Anda ingin membuat klon menggunakan konstruktor Fungsi, sesuatu seperti ini seharusnya berfungsi:

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

Tes sederhana:

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

Klon ini akan kehilangan nama dan ruang lingkupnya untuk variabel tertutup apa pun.

tobymackenzie
sumber
1

Saya telah memaksakan jawaban Jared dengan cara saya sendiri:

    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) sekarang mendukung kloning konstruktor (dapat memanggil dengan yang baru); dalam hal ini hanya membutuhkan 10 argumen (Anda dapat memvariasikannya) - karena ketidakmungkinan meneruskan semua argumen dalam konstruktor asli

2) semuanya dalam penutupan yang benar

maks. minin
sumber
bukannya arguments[0], arguments[1] /*[...]*/mengapa Anda tidak menggunakan saja ...arguments? 1) Tidak ada ketergantungan mengenai jumlah argumen (di sini terbatas pada 10) 2) lebih pendek
Vivick
Dengan menggunakan operator penyebaran, ini pasti akan menjadi metode kloning OG saya untuk fungsi, terima kasih banyak.
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();

Meskipun saya tidak akan merekomendasikan penggunaan ini, saya pikir itu akan menjadi tantangan kecil yang menarik untuk menghasilkan klon yang lebih tepat dengan mengambil beberapa praktik yang tampaknya terbaik dan memperbaikinya sedikit. Berikut hasil log:

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

Fungsi klon ini:

  1. Mempertahankan konteks.
  2. Adalah pembungkus, dan menjalankan fungsi aslinya.
  3. Menyalin properti fungsi.

Perhatikan bahwa versi ini hanya melakukan penyalinan dangkal. Jika fungsi Anda memiliki objek sebagai properti, referensi ke objek asli dipertahankan (perilaku yang sama seperti Object spread atau Object.assign). Ini berarti bahwa mengubah properti dalam di fungsi kloning akan memengaruhi objek yang direferensikan di fungsi aslinya!

David Calhoun
sumber