JavaScript: клонировать функцию

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

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

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

Спасибо заранее.

Update: Я имею в виду, что я могу делать

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

Ответ 1

попробуйте следующее:

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

Ответ 2

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

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

Однако ".bind" - это современная ( >= iE9) функция JavaScript (с совместимостью из MDN)

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

Примечание:, что он не клонирует объект функции, добавленный свойства, , включая прототип. Кредит @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

Ответ 3

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

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

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

Ответ 4

Будучи любопытным, но все еще неспособным найти ответ на тему производительности вышеприведенного вопроса, я написал этот gist для 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 Function медленнее, чем решение оболочки (и это действительно зависит от размера тела функции), он дает вам голой функциональный клон (и я имею в виду истинный мелкий клон со свойствами, но не разделенным состоянием) без лишнего путаницы со скрытыми свойствами, функциями оболочки и проблемами со стеком.

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

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

Ответ 5

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

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

Enjoy.

Ответ 6

Короче и просто:

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

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

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

Ответ 10

Я подчинил Джареда своим собственным способом:

    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) теперь он поддерживает клонирование конструкторов (может вызывать с новым); в этом случае принимает только 10 аргументов (вы можете его изменить) - из-за невозможности передать все аргументы в исходном конструкторе

2) все находится в правильных замыканиях

Ответ 11

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

Итак, нам нужен еще один, который сделает то же самое, у нас также есть ответ на этот вопрос. Просто добавьте к нему

function cloneFunction(fnToClone){
  var fnRefName = 'cloneFn';
  eval('var ' + fnRefName + ' = ' + fnToClone.toString());
return eval(fnRefName);
}

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

Ответ 12

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

Ответ 13

Этот ответ предназначен для людей, которые рассматривают клонирование функции как ответ на желаемое использование, но многим на самом деле не нужно клонировать функцию, потому что они действительно хотят просто иметь возможность присоединять различные свойства к одной и той же функции, но только объявить эту функцию один раз.

Сделайте это, создав функцию создания функции:

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

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

Ответ 14

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