JavaScript:克隆一个函数

And*_*kin 101 javascript function

什么是在JavaScript中克隆函数的最快方法(有或没有它的属性)?

我想到的两个选择是eval(func.toString())function() { return func.apply(..) }.但我担心eval的性能和包装将使堆栈变得更糟,如果应用很多或应用于已经包装,可能会降低性能.

new Function(args, body) 看起来不错,但是如何在JS中没有JS解析器的情况下可靠地将现有函数拆分为args和body?

提前致谢.

更新: 我的意思是能够做到

var funcB = funcA.clone(); // where clone() is my extension
funcB.newField = {...};    // without affecting funcA
Run Code Online (Sandbox Code Playgroud)

Pic*_*tor 96

这是一个更新的答案

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

但是".bind"是JavaScript的现代(> = iE9)功能(具有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
Run Code Online (Sandbox Code Playgroud)

注意:绑定的函数对象,instanceof将newFunc/oldFunc视为相同.感谢@Christopher

(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc;                 //gives false however
Run Code Online (Sandbox Code Playgroud)

  • 请注意,`newFunc`将没有自己的`new newFunc`实例原型,而`oldFunc`将. (2认同)
  • 实际缺点:instanceof 将无法区分 newFunc 和 oldFunc (2认同)
  • 这个答案的一个大问题是,一旦绑定,就不能再绑定第二次。后续调用 apply 也会忽略传递的“this”对象。示例:`var f = function() { console.log('hello ' + this.name) }` 当绑定到 `{name: 'Bob'}` 时打印 'hello Bob'。`f.apply({name: 'Sam'})` 还将打印 'hello Bob',忽略 'this' 对象。 (2认同)
  • 另一种需要注意的边缘情况:至少在 V8(可能还有其他引擎)中,这会改变 Function.prototype.toString() 的行为。在绑定函数上调用 .toString() 将为您提供一个类似“function () { [native code] }”的字符串,而不是完整函数的内容。 (2认同)

Jar*_*red 51

试试这个:

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));
Run Code Online (Sandbox Code Playgroud)

  • 是的,我在原帖中写过关于申请的内容.问题是像这样的包装函数破坏了它的名字,并且在许多克隆之后会慢下来. (6认同)

Jus*_*tin 18

这是Jared答案的稍微好一点的版本.克隆的次数越多,嵌套函数就越深.它总是称为原件.

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;
};
Run Code Online (Sandbox Code Playgroud)

另外,为了响应pico.creator给出的更新答案,值得注意的是,bind()Javascript 1.8.5中添加的函数与Jared的答案具有相同的问题 - 它将保持嵌套,每次使用时都会导致函数变慢.

  • 在 2019 年以后,可能更好地使用 Symbol() 而不是 __properties。 (3认同)

roy*_*ltm 8

由于好奇但仍无法找到上述问题的性能主题的答案,我为nodejs 编写了这个要点,以测试所有呈现(和评分)解决方案的性能和可靠性.

我已经比较了克隆函数创建和克隆执行的挂壁时间.结果与断言错误一起包含在要点的注释中.

再加上我的两分钱(基于作者的建议):

clone0分(更快但更丑):

Function.prototype.clone = function() {
  var newfun;
  eval('newfun=' + this.toString());
  for (var key in this)
    newfun[key] = this[key];
  return newfun;
};
Run Code Online (Sandbox Code Playgroud)

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;
};
Run Code Online (Sandbox Code Playgroud)

至于性能,如果eval/new函数比包装器解决方案慢(并且它实际上取决于函数体大小),它会为您提供裸函数克隆(我的意思是具有属性但非共享状态的真正浅层克隆)没有不必要的模糊隐藏属性,包装函数和堆栈问题.

此外,总有一个重要因素需要考虑:代码越少,错误的地方就越少.

使用eval/new函数的缺点是克隆和原始函数将在不同的范围内运行.它不适用于使用范围变量的函数.使用类似绑定的包装的解决方案与范围无关.


Max*_*gov 7

让这个方法起作用非常令人兴奋,所以它使用Function调用来复制一个函数.

关于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;
}
Run Code Online (Sandbox Code Playgroud)

请享用.


mic*_*blu 5

简短而简单:

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};
Run Code Online (Sandbox Code Playgroud)

  • 不适用于本机功能. (3认同)
  • 这个解决方案有它的位置(当你克隆用户函数而不关心使用eval时) (2认同)
  • 这也失去了功能范围。新功能可能引用在新作用域中不再存在的外部作用域var。 (2认同)

zhe*_*lin 5

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

const clonedFunction = (...args) => oldFunction(...args);
Run Code Online (Sandbox Code Playgroud)