JavaScript: клонировать функцию - PullRequest
96 голосов
/ 02 декабря 2009

Какой самый быстрый способ клонировать функцию в 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

Ответы [ 12 ]

88 голосов
/ 21 июля 2011

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

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

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

Примечание: , что новая функция эта переменная застряла с аргументом, заданным в bind (), даже при вызове новой функции apply () Кредит @ Кевин

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 как одно и то же. Кредит @ Кристофер

(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc;                 //gives false however
49 голосов
/ 02 декабря 2009

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

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));
18 голосов
/ 27 июня 2012

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

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

8 голосов
/ 23 августа 2013

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

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

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

клон0 цент (быстрее, но страшнее):

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

7 голосов
/ 22 октября 2013

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

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

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

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};
3 голосов
/ 06 августа 2018
const oldFunction = params => {
  // do something
};

const clonedFunction = (...args) => oldFunction(...args);
1 голос
/ 20 марта 2014

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

    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) все в правильных замыканиях

1 голос
/ 23 марта 2013

Если вы хотите создать клон, используя конструктор 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 голос
/ 02 декабря 2009

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

 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...