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


115

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

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

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

Заранее спасибо.

Обновление: я имею в виду, что могу делать

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

Можете привести пример, показывающий, что вы имеете в виду.
JoshBerke 02

Конечно, добавил. (Требуется 15 символов)
Андрей Щекин

Не уверен, но могу скопировать = new your_function (); работай?
Savageman 02

1
Я так не думаю, он создаст экземпляр, используя функцию в качестве конструктора
Андрей Щекин

Ответы:


54

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

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

Хорошо, так что подать заявку - это единственный способ? Я бы немного улучшил это, чтобы он не переносился дважды при двойном вызове, но в остальном - хорошо.
Андрей Щекин

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

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

Кажется, есть один способ, по крайней мере, повлиять на свойство .name следующим образом: function fa () {} var fb = function () {fa.apply (this, arguments); }; Object.defineProperties (fb, {имя: {значение: 'fb'}});
Киллрой

109

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

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

Однако .bindэто современная (> = iE9) функция JavaScript (с обходным путем совместимости от MDN )

Ноты

  1. Это не клон объекта функции дополнительные присоединенные свойства , в том числе и в прототипе собственности. Кредит для @jchook

  2. В новой функции эта переменная застревает с аргументом, указанным в 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
  1. Связанный объект функции instanceof рассматривает newFunc / oldFunc как то же самое. Кредит @Christopher
(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc; //gives false however

2
Обратите внимание, что newFuncНЕ будет собственного прототипа для new newFuncэкземпляров, а oldFuncбудет.
jchook 07

1
Практический недостаток: instanceof не сможет различить newFunc и oldFunc
Кристофер Сваси

1
@ChristopherSwasey: Это тоже может быть положительным моментом при расширении функциональности. Но, увы, это будет сбивать с толку, если не понять (добавлено в ответ)
PicoCreator

Большая проблема с этим ответом заключается в том, что после привязки вы не можете привязать второй раз. Последующие вызовы apply также игнорируют переданный объект this. Пример: var f = function() { console.log('hello ' + this.name) }при привязке {name: 'Bob'}печатает «привет, Боб». f.apply({name: 'Sam'})также напечатает "hello Bob", игнорируя объект "this".
Кевин Муни

1
Еще один крайний случай, на который следует обратить внимание: по крайней мере, в V8 (и, возможно, в других движках) это меняет поведение Function.prototype.toString (). Вызов .toString () для связанной функции даст вам строку вроде function () { [native code] }вместо полного содержимого функции.
GladstoneKeep

19

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

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, имеет ту же проблему, что и ответ Джареда - она ​​будет продолжать вложение, вызывая все более и более медленные функции каждый раз, когда она используется.


в 2019+, возможно, лучше использовать Symbol () вместо __properties.
Alexander Mills

10

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

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

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

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 является то, что клон и исходная функция будут работать в разных областях. Это не будет хорошо работать с функциями, которые используют переменные с ограниченным объемом. Решения, использующие привязку, подобную связыванию, не зависят от области действия.


Помните, что eval и новая функция не эквивалентны. eval работает в локальной области, а функция - нет. Это может привести к проблемам с привязкой к другим переменным из кода функции. См. Perfectionkills.com/global-eval-what-are-the-options для подробного объяснения.
Pierre

Правильно, используя eval или новую функцию, вы не можете клонировать функцию вместе с ее исходной областью видимости.
royaltm

На самом деле: если вы добавляете Object.assign(newfun.prototype, this.prototype);перед оператором return (чистая версия), ваш метод - лучший ответ.
Vivick

9

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

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

Наслаждаться.


5

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

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

1
Кроме того, он использует вариант eval под капотом, которого лучше избегать по ряду причин (здесь не будем вдаваться в подробности, он описан в тысячах других мест).
Эндрю Фолкнер

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

2
Это также теряет область видимости функции. Новая функция может ссылаться на переменные внешней области видимости, которые больше не существуют в новой области.
trusktr


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

Обратите внимание, что это неполно. Это скопирует свойства из originalFunction, но на самом деле не будет выполняться при запуске clonedFunction, что неожиданно.
Дэвид Калхун,

2

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

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

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функции.


1

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

 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
Если есть причина изменить поля самой функции (автономный кеш, «статические» свойства), то возникает ситуация, когда я хочу клонировать функцию и изменить ее, не затрагивая исходную.
Андрей Щекин

Я имею в виду свойства самой функции.
Андрей Щекин

1
функции могут иметь свойства, как и любой объект, поэтому
Раду Симионеску

1

Если вы хотите создать клон с помощью конструктора 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]);
        }
    }
})()

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


1

Я по-своему усложнил ответ Джареда:

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

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


вместо того arguments[0], arguments[1] /*[...]*/, чтобы просто не использовать ...arguments? 1) Нет зависимости относительно количества аргументов (здесь ограничено 10) 2) короче
Вивик

С использованием оператора распространения, это определенно был бы мой метод клонирования OG для функций, спасибо большое.
Вивик

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

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

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

Эта функция клонирования:

  1. Сохраняет контекст.
  2. Является оболочкой и запускает исходную функцию.
  3. Копирует свойства функции.

Обратите внимание, что эта версия выполняет только поверхностное копирование. Если ваша функция имеет объекты как свойства, ссылка на исходный объект сохраняется (такое же поведение, как Object spread или Object.assign). Это означает, что изменение глубоких свойств в клонированной функции повлияет на объект, на который есть ссылка в исходной функции!

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.