JavaScript:克隆函数


115

在JavaScript中克隆功能(具有或不具有其属性)的最快方法是什么?

两个选项来脑海中eval(func.toString())function() { return func.apply(..) }。但是我担心评估和包装的性能会使堆栈变得更糟,并且如果大量应用或应用于已包装的包装,可能会降低性能。

new Function(args, body) 看起来不错,但是如果没有JS中的JS解析器,我怎么能可靠地将现有函数拆分为args和body?

提前致谢。

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

var funcB = funcA.clone(); // where clone() is my extension
funcB.newField = {...};    // without affecting funcA

您能否举一个例子说明您的意思。
JoshBerke,2009年

当然可以。(必填15个字符)
Andrey Shchekin,2009年

我不确定,但是可以复制= new your_function(); 工作?
Savageman

1
我不这么认为,它将使用函数作为构造函数创建一个实例
Andrey Shchekin

Answers:


54

试试这个:

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

好的,所以申请是唯一的方法吗?我会对此进行一些改进,以使其在调用两次时不会包装两次,否则可以。
2009年

apply用于轻松传递参数。同样,这将适用于您要克隆构造函数的实例。
杰瑞德(Jared)2009年

6
是的,我在原始帖子中写了关于apply的文章。问题是这样的包装函数会破坏其名称,并且在进行多次克隆后会变慢。
Andrey Shchekin

似乎至少有一种方法可以像这样影响.name属性:function fa(){} var fb = function(){fa.apply(this,arguments); }; Object.defineProperties(fb,{名称:{值:'fb'}});
Killroy

109

这是更新的答案

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

但是,这.bind是JavaScript的现代(> = iE9)功能(具有MDN兼容性解决方法

笔记

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

  2. 即使在新函数apply()调用上,变量的新函数也受bind()上给定的参数的约束。感谢@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
  1. 绑定函数对象instanceof将newFunc / oldFunc视为相同。感谢@Christopher
(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc; //gives false however

2
注意,实例newFunc将没有自己的原型。new newFuncoldFunc
jchook

1
实际的缺点:instanceof无法区分newFunc和oldFunc
Christopher Swasey

1
@ChristopherSwasey:扩展功能时,实际上也可能是一个好处。但很可惜,它会混乱,如果不是深知(添加到答案)
PicoCreator

这个答案的一个大问题是,一旦绑定,就无法再次绑定。随后的应用调用也忽略了传递的“ this”对象。示例:var f = function() { console.log('hello ' + this.name) }绑定到时会{name: 'Bob'}打印“ hello Bob”。 f.apply({name: 'Sam'})也会打印“ hello Bob”,忽略“ this”对象。
凯文·穆尼

1
需要注意的另一种极端情况:至少在V8(可能还有其他引擎)中,这会更改Function.prototype.toString()的行为。在绑定函数上调用.toString()会给您一个字符串,function () { [native code] }而不是完整函数的内容。
GladstoneKeep

19

这是杰瑞德(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。
亚历山大·米尔斯

10

出于好奇,但仍然无法找到上述问题的性能主题的答案,因此我为nodejs 编写了要点,以测试所有提出(和评分)解决方案的性能和可靠性。

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

再加上我的两分钱(根据作者的建议):

clone0分(更快但更难看):

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

clone4美分(慢一些,但对于那些不喜欢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函数的不利之处在于,克隆函数和原始函数将在不同的范围内运行。它不能与使用范围变量的函数配合使用。使用类似绑定的包装的解决方案与范围无关。


注意,eval和new Function不等效。eval opers在本地范围内,而Function则不在。这可能会导致在从功能代码内部访问其他变量方面出现问题。有关详细说明,请参见perfectionkills.com/global-eval-what-are-the-options
Pierre

正确,并且使用eval或new Function不能将函数及其原始作用域一起克隆。
royaltm 2013年

实际上:一旦Object.assign(newfun.prototype, this.prototype);在return语句(干净版本)之前添加,您的方法就是最佳答案。
Vivick '17

9

使此方法起作用非常令人兴奋,因此它使用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;
}

请享用。


5

简短:

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};

1
而且,它在引擎盖下使用了eval的变体,出于各种原因,最好避免使用它(这里不会涉及到它,它在其他数千个地方都涵盖了)。
安德鲁·福克纳

2
此解决方案有其用处(当您要克隆用户功能并且不在乎使用eval时)
Lloyd

2
这也失去了功能范围。新功能可能引用在新作用域中不再存在的外部作用域var。
trusktr '16

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

const clonedFunction = (...args) => oldFunction(...args);

3
const clonedFunction = Object.assign(() => {}, originalFunction);

请注意,这是不完整的。这会从复制属性originalFunction,但在运行时实际上不会执行它clonedFunction,这是意外的。
David Calhoun

2

该答案适用于将克隆功能视为其所需用法的答案的人,但实际上并不需要克隆功能的人,因为他们真正想要的只是能够将不同的属性附加到同一功能上,而仅仅是一次声明该函数。

通过创建一个函数创建函数来做到这一点:

function createFunction(param1, param2) {
   function doSomething() {
      console.log('in the function!');
   }
   // Assign properties to `doSomething` if desired, perhaps based
   // on the arguments passed into `param1` and `param2`. Or,
   // even return a different function from among a group of them.
   return doSomething;
};

let a = createFunction();
a.something = 1;
let b = createFunction();
b.something = 2; // does not overwrite a.something
console.log(a.something);
a();
b();

这与您所概述的并不完全相同,但是,这取决于您要如何使用希望克隆的功能。这还会占用更多的内存,因为它实际上创建了该函数的多个副本,每次调用一次。但是,该技术可以解决某些人的用例,而无需复杂的clone功能。


1

只是想知道-当您拥有原型并且为什么可以将函数调用的范围设置为您想要的任何内容时,为什么要克隆一个函数?

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

1
如果有理由更改函数本身的字段(自包含缓存,“静态”属性),那么在某些情况下,我想克隆一个函数并对其进行修改而不影响原始函数。
2009年

我的意思是函数本身的属性。
2009年

1
函数可以像任何对象一样具有属性,这就是为什么
Radu Simionescu 2012年

1

如果要使用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

我以自己的方式提高了Jared的答案:

    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)现在支持克隆构造函数(可以用new调用);在这种情况下,仅需要10个参数(可以更改)-由于无法在原始构造函数中传递所有参数

2)一切都正确关闭


而不是arguments[0], arguments[1] /*[...]*/为什么不简单地使用...arguments?1)关于参数的数量(此处限制为10)没有依赖性2)更短
Vivick

通过使用spread运算符,这肯定是我的函数的OG克隆方法,这很多。
Vivick

0
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

0
const clone = (fn, context = this) => {
  // Creates a new function, optionally preserving desired context.
  const newFn = fn.bind(context);

  // Shallow copies over function properties, if any.
  return Object.assign(newFn, fn);
}

// Usage:

// Setup the function to copy from.
const log = (...args) => console.log(...args);
log.testProperty = 1;

// Clone and make sure the function and properties are intact.
const log2 = clone(log);
log2('foo');
// -> 'foo'
log2.testProperty;
// -> 1

// Make sure tweaks to the clone function's properties don't affect the original function properties.
log2.testProperty = 2;
log2.testProperty;
// -> 2
log.testProperty;
// -> 1

此克隆功能:

  1. 保留上下文。
  2. 是包装器,并运行原始功能。
  3. 复制功能属性。

请注意,此版本仅执行浅表复制。如果您的函数将对象作为属性,则保留对原始对象的引用(与Object spread或Object.assign相同的行为)。这意味着更改克隆函数中的深层属性将影响原始函数中引用的对象!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.