web-dev-qa-db-ja.com

JavaScript:関数のクローン

JavaScriptで関数を複製する(プロパティの有無にかかわらず)最速の方法は何ですか?

頭に浮かぶ2つのオプションは、eval(func.toString())function() { return func.apply(..) }です。しかし、evalのパフォーマンスが心配であり、ラッピングはスタックを悪化させ、大量に適用した場合や既にラップされている場合にパフォーマンスを低下させる可能性があります。

new Function(args, body)は素晴らしく見えますが、JSのJSパーサーを使用せずに、既存の関数を引数と本文に確実に分割するにはどうすればよいですか?

前もって感謝します。

更新:できることは

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

これを試して:

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

ここに更新された答えがあります

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

ただし、「。bind」はJavaScriptの最新(> = iE9)機能です(MDNからの互換性回避策があります)

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

注:それはクローンを作成しません追加された関数オブジェクトpropertiesincludeprototypeプロパティ。 @ jchook へのクレジット

注:新しい関数this変数は、新しい関数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

注:バインドされた関数オブジェクト、instanceofはnewFunc/oldFuncを同じものとして扱います。 @ Christopher へのクレジット

(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc;                 //gives false however
91
PicoCreator

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によって提供される更新された回答に応じて、Javascript 1.8.5で追加されたbind()関数にはJaredの回答と同じ問題があることに注意してください。使用されるたびに機能します。

18

好奇心はあるものの、上記の質問のパフォーマンストピックへの答えがまだ見つからないので、私はこれを書いた js nodejsで、提示された(およびスコア付けされた)すべてのソリューションのパフォーマンスと信頼性の両方をテストします。

クローン関数の作成とクローンの実行のウォールタイムを比較しました。結果とアサーションエラーは、Gistのコメントに含まれています。

プラス私の2セント(著者の提案に基づく):

clone0セント(高速ですがbutい):

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がラッパーソリューションよりも遅い場合(そして実際に関数本体のサイズに依存する場合)、不要なファズのない裸の関数クローン(およびプロパティを持つ非共有状態の真の浅いクローン)を提供します隠されたプロパティ、ラッパー関数、スタックの問題。

さらに、考慮すべき重要な要素が常に1つあります。コードが少ないほど、ミスが少なくなります。

Eval/new関数を使用することの欠点は、クローンと元の関数が異なるスコープで動作することです。スコープ変数を使用している関数ではうまく機能しません。バインドのようなラッピングを使用するソリューションは、スコープに依存しません。

8
royaltm

このメソッドを機能させるのは非常にエキサイティングだったので、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;
}

楽しい。

7
Max Dolgov

短くて簡単:

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};
5
micahblu
const oldFunction = params => {
  // do something
};

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

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
tobymackenzie

ただ疑問に思う-プロトタイプがあり、関数呼び出しのスコープを希望するものに設定できるのに、なぜ関数を複製したいのでしょうか?

 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
Upperstage

私はジャレッドの答えを自分のやり方で台無しにしました。

    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)すべてが正しい閉鎖状態にある

1
max.minin
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

この回答は、目的の使用法に対する答えとして関数のクローンを作成したが、実際には実際には多くの人が関数をクローンする必要がない人本当に欲しいのは、同じ関数に異なるプロパティをアタッチできるようにするだけで、その関数を一度だけ宣言することです。

これを行うには、関数作成関数を作成します。

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

これは、説明したものとまったく同じではありませんが、クローンを作成する関数の使用方法によって異なります。また、これは、呼び出しごとに1回、関数の複数のコピーを実際に作成するため、より多くのメモリを使用します。ただし、この手法は、複雑なclone関数を必要とせずに、一部の人々のユースケースを解決する場合があります。

0
ErikE