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