JavaScript: клонирование функции


каков самый быстрый способ клонирования функции в JavaScript (с ее свойствами или без них)?

на ум приходят два варианта eval(func.toString()) и function() { return func.apply(..) }. Но я беспокоюсь о производительности eval и обертывания сделает стек хуже и, вероятно, ухудшит производительность, если применяется много или применяется к уже завернутому.

new Function(args, body) выглядит красиво, но как именно я могу надежно разделить существующую функцию на args и body без парсера JS в JS?

спасибо предварительно.

обновление: То, что я имею в виду, это возможность сделать

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

11 ответов:

попробуйте это:

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

вот обновленный ответ

var newFunc = oldFunc.bind({}); //clones the function with '{}' acting as it's new 'this' parameter
".bind " - это современная (>=iE9 ) функция JavaScript (с обходным решением совместимости от MDN)

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

Примечание: что это не клон функция объекта дополнительно прилагается свойства,в том числе the прототип свойство. Кредит на @jchook

Примечание:, что новая функция этой переменная застряла с аргументом, заданным на bind (), даже на новых вызовах функции apply (). Кредит на @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

Примечание: привязанный объект функции, instanceof обрабатывает newFunc / oldFunc как то же самое. Кредит на @Christopher

(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc;                 //gives false however

вот немного лучшая версия ответа Джареда. Это не будет в конечном итоге с глубоко вложенными функциями, чем больше вы клонируете. Он всегда называет оригинал.

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

кроме того, в ответ на обновленный ответил Пико.создатель, стоит отметить, что bind() функция, добавленная в Javascript 1.8.5, имеет ту же проблему, что и ответ Джареда - она будет продолжать вложенность, вызывая все более медленные функции каждый раз, когда она используется.

будучи любопытным, но все еще не в состоянии найти ответ на тему производительности вопроса выше, я написал это суть для nodejs, чтобы проверить как производительность, так и надежность всех представленных (и забитых) решений.

Я сравнил время стены создания функции клона и выполнения клона. Результаты вместе с ошибками утверждения включены в комментарий gist.

плюс мои два цента (на основе авторской предложение):

clone0 cent (быстрее, но уродливее):

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

clone4 cent (медленнее, но для тех, кто не любит eval() для целей, известных только им и их предкам):

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

что касается производительности, если функция eval / new медленнее, чем решение обертки (и это действительно зависит от размера тела функции), она дает вам голый клон функции (и я имею в виду истинный мелкий клон со свойствами, но неразделенным состоянием) без ненужного пуха со скрытым свойства, функции оболочки и проблемы со стеком.

плюс всегда есть один важный фактор нужно учитывать: чем меньше кода, тем меньше места для ошибки.

недостатком использования функции eval / new является то, что клон и исходная функция будут работать в разных областях. Это не будет хорошо работать с функциями, которые используют переменные области. Решения, использующие оболочку типа bind, не зависят от области применения.

было довольно интересно заставить этот метод работать, поэтому он делает клон функции с помощью вызова функции.

некоторые ограничения на замыкания, описанные в ссылка на функцию 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;
}

наслаждайтесь.

коротко и просто:

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};
const oldFunction = params => {
  // do something
};

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

просто интересно - зачем вам клонировать функцию, когда у вас есть прототипы и вы можете установить область вызова функции на все, что пожелаете?

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

Если вы хотите создать клон с помощью конструктора функций, что-то вроде этого должно работать:

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

простой тест:

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

эти клоны потеряют свои имена и область действия для любых закрытых переменных.

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

хотя я никогда не рекомендовал бы использовать это, я подумал, что было бы интересной небольшой проблемой придумать более точный клон, взяв некоторые из методов, которые казались лучшими, и немного исправив его. Вот результат логов:

Equal?  false
Names:  myFunction myFunction
{ [Function: myFunction] value: 'something' }
{ [Function: myFunction] value: 'something', another: 'somethingelse' }
InstanceOf?  false
Called Function
Called Function