可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
What is a fastest way to clone a function in JavaScript (with or without its properties)?
Two options coming to mind are eval(func.toString())
and function() { return func.apply(..) }
. But I am worried about performance of eval and wrapping will make stack worse and will probably degrade performance if applied a lot or applied to already wrapped.
new Function(args, body)
looks nice, but how exactly can I reliable split existing function to args and body without a JS parser in JS?
Thanks in advance.
Update:
What I mean is being able to do
var funcB = funcA.clone(); // where clone() is my extension
funcB.newField = {...}; // without affecting funcA
回答1:
try this:
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:
Here is an updated answer
var newFunc = oldFunc.bind({}); //clones the function with '{}' acting as it's new 'this' parameter
However ".bind" is a modern ( >=iE9 ) feature of JavaScript (with a compatibility workaround from MDN)
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Note: that it does not clone the function object additional attached properties, including the prototype property. Credit to @jchook
Note: that the new function this variable is stuck with the argument given on bind(), even on new function apply() calls. Credit to @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
Note: binded function object, instanceof treats newFunc/oldFunc as the same. Credit to @Christopher
(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc; //gives false however
回答3:
Here's a slightly better version of Jared's answer. This one won't end up with deeply nested functions the more you clone. It always calls the original.
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;
};
Also, in response to the updated answer given by pico.creator, it is worth noting that the bind()
function added in Javascript 1.8.5 has the same problem as Jared's answer - it will keep nesting causing slower and slower functions each time it is used.
回答4:
Being curious but still unable to find the answer to the performance topic of the question above, I wrote this gist for nodejs to test both the performance and reliability of all presented (and scored) solutions.
I've compared the wall times of a clone function creation and the execution of a clone.
The results together with assertion errors are included in the gist's comment.
Plus my two cents (based on the author's suggestion):
clone0 cent (faster but uglier):
Function.prototype.clone = function() {
var newfun;
eval('newfun=' + this.toString());
for (var key in this)
newfun[key] = this[key];
return newfun;
};
clone4 cent (slower but for those who dislike eval() for purposes known only to them and their ancestors):
Function.prototype.clone = function() {
var newfun = new Function('return ' + this.toString())();
for (var key in this)
newfun[key] = this[key];
return newfun;
};
As for the performance, if eval/new Function is slower than wrapper solution (and it really depends on the function body size), it gives you bare function clone (and I mean the true shallow clone with properties but unshared state) without unnecessary fuzz with hidden properties, wrapper functions and problems with stack.
Plus there is always one important factor you need to take into consideration: the less code, the less places for mistakes.
The downside of using the eval/new Function is that the clone and the original function will operate in different scopes. It won't work well with functions that are using scoped variables. The solutions using bind-like wrapping are scope independent.
回答5:
It was pretty exciting to make this method work, so it makes a clone of a function using Function call.
Some limitations about closures described at MDN Function Reference
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:
Short and simple:
Function.prototype.clone = function() {
return new Function('return ' + this.toString())();
};
回答7:
const oldFunction = params => {
// do something
};
const clonedFunction = (...args) => oldFunction(...args);
回答8:
Just wondering - why would you want to clone a function when you have prototypes AND can set the scope of a function call to anything you wish?
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:
If you want to create a clone using the Function constructor, something like this should work:
_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;
}
A simple test:
(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]);
}
}
})()
These clones will lose their names and scope for any closed over variables though.
回答10:
I've impoved Jared's answer in my own manner:
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) now it supports cloning of constructors (can call with new); in that case takes only 10 arguments (you can vary it) - due to impossibility of passing all arguments in original constructor
2) everything is in correct closures
回答11:
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();
While I would never recommend using this, I thought it would be an interesting little challenge to come up with a more precise clone by taking some of the practices that seemed to be the best and fixing it up a bit. Heres the result of the logs:
Equal? false
Names: myFunction myFunction
{ [Function: myFunction] value: 'something' }
{ [Function: myFunction] value: 'something', another: 'somethingelse' }
InstanceOf? false
Called Function
Called Function