JavaScript:克隆一个函数

IT技术 javascript function
2021-01-14 15:31:17

在 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
6个回答

这是更新的答案

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

然而,这.bind是 JavaScript 的现代 ( >=iE9 ) 特性(具有来自 MDN兼容性解决方法

笔记

  1. 并不克隆的功能对象的附加连接属性包括所述原型属性。感谢@jchook

  2. 即使在新函数调用中新函数this变量也会被 上给出的参数卡住感谢@Kevinbind()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
  1. 绑定函数对象,instanceofnewFunc/oldFunc视为相同。感谢@Christopher
(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc; //gives false however
实际缺点:instanceof 将无法区分 newFunc 和 oldFunc
2021-03-19 15:31:17
这个答案的一个大问题是,一旦绑定,就不能再次绑定。对 apply 的后续调用也会忽略传递的“this”对象。示例:var f = function() { console.log('hello ' + this.name) }绑定到{name: 'Bob'}打印 'hello Bob' 时。 f.apply({name: 'Sam'})还将打印 'hello Bob',忽略 'this' 对象。
2021-03-21 15:31:17
请注意,实例newFunc不会有自己的原型new newFunc,而oldFunc会。
2021-03-23 15:31:17
@ChristopherSwasey:在扩展功能时,它实际上也有好处。但是,唉,如果没有很好地理解它会令人困惑(添加到答案中)
2021-03-24 15:31:17
另一个需要注意的边缘情况:至少在 V8(可能还有其他引擎)中,这会改变 Function.prototype.toString() 的行为。在绑定函数上调用 .toString() 会给你一个字符串,function () { [native code] }而不是完整函数的内容。
2021-04-12 15:31:17

试试这个:

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 用于轻松传递参数。此外,这适用于您想要克隆构造函数的情况。
2021-03-31 15:31:17
好的,所以申请是唯一的方法吗?我会稍微改进一下,这样它在被调用两次时不会包装两次,否则,好的。
2021-04-02 15:31:17
似乎有一种方法至少可以像这样影响 .name 属性: function fa () {} var fb = function() { fa.apply(this, arguments); }; Object.defineProperties(fb, { name: { value: 'fb' } });
2021-04-03 15:31:17
是的,我在原帖中写了关于申请。问题是这样的包装函数会破坏它的名字,并且在多次克隆后会变慢。
2021-04-09 15:31:17

这是 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;
};

此外,针对 pico.creator 给出的更新答案,值得注意的是,bind()Javascript 1.8.5 中添加函数与 Jared 的答案存在相同的问题——每次使用时,它都会不断嵌套,导致函数越来越慢。

在 2019 年以后,最好使用 Symbol() 而不是 __properties。
2021-03-17 15:31:17

出于好奇但仍然无法找到上述问题的性能主题的答案,我为 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 函数的缺点是克隆和原始函数将在不同的范围内运行。它不适用于使用作用域变量的函数。使用类绑定包装的解决方案与范围无关。

事实上:一旦您Object.assign(newfun.prototype, this.prototype);在 return 语句(干净版本)之前添加,您的方法就是最好的答案。
2021-03-16 15:31:17
请注意, eval 和 new Function 不是等价的。eval 在本地范围内操作,但 Function 没有。这可能会导致从函数代码内部访问其他变量时出现问题。有关详细解释,请参阅perfectkills.com/global-eval-what-are-the-options
2021-03-29 15:31:17
是的,通过使用 eval 或 new 函数,您不能将函数与其原始作用域一起克隆。
2021-04-04 15:31:17

使这个方法工作非常令人兴奋,因此它使用函数调用克隆了一个函数。

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

享受。