JavaScript:関数の複製


115

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

心に来る二つの選択肢がある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

意味を示す例を挙げてください。
JoshBerke、2009

はい、追加しました。(15文字必須)
Andrey Shchekin、2009

わかりませんが、= new your_function();をコピーできます。作業?
Savageman、2009

1
私はそうは思いません、それはコンストラクタとして関数を使用してインスタンスを作成します
Andrey Shchekin

回答:


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

わかりました、それで適用は唯一の方法ですか?これを少し改善して、2回呼び出されたときに2回ラップされないようにしますが、それ以外の場合は問題ありません。
Andrey Shchekin、2009

applyは、引数を簡単に渡すために使用されます。また、これは、コンストラクタを複製するインスタンスで機能します。
Jared、

6
はい、私は元の投稿で申請について書きました。問題は、このようなラッピング関数がその名前を破壊し、多くのクローンの後で遅くなることです。
Andrey Shchekin、2009

次のように、少なくとも.nameプロパティに影響を与える方法が1つあるようです。function fa(){} var fb = function(){fa.apply(this、arguments); }; Object.defineProperties(fb、{name:{value: 'fb'}});
Killroy

109

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

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

注:それはクローンを作成しない関数オブジェクトの追加接続のプロパティを含むプロトタイプのプロパティを。@jchookへのクレジット

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

2
ただし、インスタンスnewFunc用の独自のプロトタイプはありませんがnew newFunc、ありoldFuncます。
jchook 2013

1
実用的な欠点:instanceofはnewFuncとoldFuncを区別できません
Christopher Swasey

1
@ChristopherSwasey:機能を拡張するとき、それは実際に同様に有利になる可能性があります。しかし、悲しいかな、よく理解されていないと混乱を招きます(回答に追加されます)
PicoCreator 2014

この回答の大きな問題は、いったんバインドすると、2回目にバインドできないことです。後続の適用の呼び出しでも、渡された「this」オブジェクトは無視されます。例:「hello Bob」var f = function() { console.log('hello ' + this.name) }{name: 'Bob'}印刷するようにバインドされている場合。 f.apply({name: 'Sam'})'this'オブジェクトを無視して、 'hello Bob'も出力します。
ケビンムーニー

1
注意すべきもう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+では、おそらく__propertiesの代わりにSymbol()を使用する方が良いでしょう。
Alexander Mills

10

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

クローン関数の作成と実行の実時間を比較しました。結果とアサーションエラーは要旨のコメントに含まれています。

さらに、私の2セント(著者の提案に基づく):

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

さらに、考慮する必要がある重要な要素が常に1つあります。コードが少ないほど、間違いの場所が少なくなります。

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


evalとnew​​ Functionは同等ではないことに注意してください。evalはローカルスコープで動作しますが、関数は動作しません。これは、関数コード内から他の変数にアクセスすることに結びつく問題につながる可能性があります。詳しい説明については、perfectionkills.com / global-eval-what-are-the-optionsを参照してください。
ピエール

正しく、evalまたはnew Functionを使用して、元のスコープと一緒に関数を複製することはできません。
royaltm 2013

実際のところObject.assign(newfun.prototype, this.prototype);、returnステートメント(クリーンバージョン)の前に追加すると、あなたのメソッドが最良の答えになります。
Vivick 2017

9

このメソッドを機能させるのはとてもエキサイティングだったので、関数呼び出しを使用して関数のクローンを作成します。

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のバリアントを使用していますが、これはさまざまな理由で回避するのが最善です(ここでは説明しませんが、他の何千もの場所でカバーされています)。
Andrew Faulkner、

2
このソリューションは適切です(ユーザー関数のクローンを作成していて、evalが使用されていることを気にしない場合)
ロイド

2
これも関数のスコープを失います。新しい関数は、新しいスコープに存在しない外側のスコープ変数を参照する場合があります。
trusktr

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

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

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

これは不完全であることに注意してください。これはからプロパティをコピーしますがoriginalFunction、を実行しても実際には実行されませんclonedFunction。これは予想外です。
David Calhoun

2

この答えは、関数のクローンを目的の使用法に対する答えと見なしているが、実際には関数のクローンを作成する必要のない人が対象です。その関数を1回宣言します。

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

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機能を必要とせずに一部の人々のユースケースを解決する場合があります。


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
関数自体のフィールドを変更する理由がある場合(自己完結型キャッシュ、「静的」プロパティ)、元の関数に影響を与えずに関数を複製して変更したい場合があります。
Andrey Shchekin、2009

私は関数自体の特性を意味します。
Andrey Shchekin、2009

1
関数は、任意のオブジェクトのようなプロパティを持つことができます。それが理由です
Radu Simionescu

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

スプレッド演算子を使用すると、これは間違いなく関数のOGクローン作成方法になります。
Vivick 2017

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.assignと同じ動作)。これは、クローンされた関数のディーププロパティを変更すると、元の関数で参照されているオブジェクトに影響することを意味します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.