オリジナルを参照しながらJavaScript関数をオーバーライドする


160

a()オーバーライドしたい関数がありますがa()、コンテキストに応じた順序でオリジナルを実行することもできます。たとえば、ページを生成しているときに、次のようにオーバーライドしたい場合があります。

function a() {
    new_code();
    original_a();
}

そして時々このように:

function a() {
    original_a();
    other_new_code();
}

オーバーライドoriginal_a()内からどのように取得しa()ますか?可能ですか?

このようにオーバーライドする代わりに提案しないでください、私は多くを知っています。この方法について具体的に質問します。

回答:


179

あなたはこのようなことをすることができます:

var a = (function() {
    var original_a = a;

    if (condition) {
        return function() {
            new_code();
            original_a();
        }
    } else {
        return function() {
            original_a();
            other_new_code();
        }
    }
})();

original_a無名関数の内部で宣言すると、グローバル名前空間が乱雑になることを防ぎますが、内部関数で使用できます。

コメントで述べたNerdmasterのように、必ず()最後にを含めてください。外部関数自体をに保存するのではなく、外部関数を呼び出して結果(2つの内部関数の1つ)をaに保存しaます。


25
私のような馬鹿については、最後の "()"に細心の注意を払ってください-それなしでは、内部関数ではなく外部関数を返します:)
Nerdmaster

@Nerdmaster指摘してくれてありがとう。私は人々が気付くのを助けるためにメモを追加しました。
Matthew Crumley、2012

5
うわー、名前空間なしでこれをやろうとしたところ、スタックオーバーフローが発生しました(笑)。
gosukiwi 2012

関数から最初と最後の括弧を削除した場合、結果は同じになると思います。ここから好き(functi..})。ただ、やっ}();て、同じ結果を生成する(function{})()括弧の第一セットは、あなただけの宣言カントので、匿名、あなたが持っている関数式はそれを割り当てるために使用されています。しかし()を実行することで、関数を返すだけでは何も定義しません。
Muhammad Umer 2013

@MuhammadUmer関数式を囲む括弧が不要であるというのはあなたの言うとおりです。それらを含めなかったのは、最初の数行だけを見ると、(少なくとも私には)外部関数をに直接割り当てているように見え、それをa呼び出したり、戻り値を格納したりしていないためです。括弧は私に何か他のことが起こっていることを知らせました。
Matthew Crumley 2013

88

プロキシパターンはあなたを助けるかもしれません。

(function() {
    // log all calls to setArray
    var proxied = jQuery.fn.setArray;
    jQuery.fn.setArray = function() {
        console.log( this, arguments );
        return proxied.apply( this, arguments );
    };
})();

上記は、コードを関数にラップして、「プロキシされた」変数を非表示にします。jQueryのsetArray-methodをクロージャーに保存し、それを上書きします。次に、プロキシはメソッドへのすべての呼び出しを記録し、呼び出しを元の呼び出しに委任します。apply(this、arguments)を使用すると、呼び出し元が元のメソッドとプロキシされたメソッドの違いに気付かないことが保証されます。


10
確かに。「適用」と「引数」の使用により、これは他の回答よりもはるかに堅牢になります
Robert Levy

このパターンはjQueryを必要としないことに注意してください。jQueryの関数の1つを例として使用しているだけです。
ケブ

28

本当にありがとうございますプロキシパターンは本当に役に立ちました.....実際に私はグローバル関数fooを呼び出したかったです。特定のページでは、いくつかのチェックを行う必要があります。だから私は次のことをしました。

//Saving the original func
var org_foo = window.foo;

//Assigning proxy fucnc
window.foo = function(args){
    //Performing checks
    if(checkCondition(args)){
        //Calling original funcs
        org_foo(args);
    }
};

Thnxこれは本当に私を助けました


3
プロキシパターンをたどる場合、このAnswerのコードにはないこの詳細を実装することが重要な場合がorg_foo(args)ありますorg_foo.call(this, args)。ではなく、を呼び出します。これはthis、window.fooが通常どおり(プロキシされずに)呼び出した場合と同じように維持されます。この回答を
cellepo 2016

10

次のような構成を使用して関数をオーバーライドできます。

function override(f, g) {
    return function() {
        return g(f);
    };
}

例えば:

 a = override(a, function(original_a) {
      if (condition) { new_code(); original_a(); }
      else { original_a(); other_new_code(); }
 });

編集:タイプミスを修正しました。


+1これは、他のプロキシパターンの例よりもはるかに読みやすくなっています。
Mu Mind、

1
...しかし、私が誤解していない場合、これは引数がゼロの関数、または少なくともいくつかの所定の数の引数に制限されています。読みやすさを失わずに任意の数の引数に一般化する方法はありますか?
Mu Mind、

@MuMind:はい、ただしプロキシパターンを適切に使用- 私の回答を参照しください。
Dan Dascalescu 2014年

4

任意の引数を渡す:

a = override(a, function(original_a) {
    if (condition) { new_code(); original_a.apply(this, arguments) ; }
    else { original_a.apply(this, arguments); other_new_code(); }
});

1
引数は参照しoriginal_aませんか?
ジョナサン。

2

@Matthew Crumleyが提供する答えは、すぐに呼び出された関数式を利用して、返された関数の実行コンテキストに古い 'a'関数を閉じることです。これが最良の答えだと思いますが、個人的には、関数 'a'をIIFEの引数として渡したいと思います。もっと分かりやすいと思います。

   var a = (function(original_a) {
        if (condition) {
            return function() {
                new_code();
                original_a();
            }
        } else {
            return function() {
                original_a();
                other_new_code();
            }
        }
    })(a);

1

上記の例は、関数オーバーライドに正しく適用thisまたは渡されませんarguments。アンダースコア_.wrap()は既存の関数をラップし、正しく適用thisして渡しargumentsます。参照:http : //underscorejs.org/#wrap


0

他の人が書いたコードがあり、コードに見つからない行を関数に追加したいと思っていました。回避策として、私はそれを上書きしたかったのです。

しかし、解決策はどれも私にとってはうまくいきませんでした。

これが私の場合に機能したものです:

if (typeof originalFunction === "undefined") {
    originalFunction = targetFunction;
    targetFunction = function(x, y) {
        //Your code
        originalFunction(a, b);
        //Your Code
    };  
}

0

複数のライブラリの関数をオーバーライドする必要があることが多かったため、同様のシナリオで小さなヘルパーを作成しました。このヘルパーは、「名前空間」(関数コンテナー)、関数名、およびオーバーライドする関数を受け入れます。参照されている名前空間の元の関数を新しい関数に置き換えます。

新しい関数は、元の関数を最初の引数として受け入れ、元の関数の引数を残りの引数として受け入れます。常にコンテキストを保持します。void関数とnon-void関数もサポートしています。

function overrideFunction(namespace, baseFuncName, func) {
    var originalFn = namespace[baseFuncName];
    namespace[baseFuncName] = function () {
        return func.apply(this, [originalFn.bind(this)].concat(Array.prototype.slice.call(arguments, 0)));
    };
}

Bootstrapの使用例:

overrideFunction($.fn.popover.Constructor.prototype, 'leave', function(baseFn, obj) {
    // ... do stuff before base call
    baseFn(obj);
    // ... do stuff after base call
});

ただし、パフォーマンステストは作成しませんでした。シナリオによっては、大したことのない不要なオーバーヘッドが追加される可能性があります。


0

私の意見では、上位の回答は読み取り可能/保守可能ではなく、他の回答はコンテキストを適切にバインドしていません。ES6構文を使用してこれらの両方の問題を解決する、読みやすいソリューションを次に示します。

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};

しかし、ES6構文を使用すると、使用がかなり制限されます。ES5で動作するバージョンはありますか?
eitch

0

したがって、私の答えは、元のオブジェクトを指す_this変数を使用できるようにするソリューションになりました。「Square」の新しいインスタンスを作成しますが、「Square」がそのサイズを生成する方法が嫌いです。私はそれが私の特定のニーズに従うべきだと思いました。ただし、これを行うには、this.height、this.GetVolume()など、正方形にすでに存在する他の関数を呼び出す関数の内部を含む更新された「GetSize」関数を正方形に含める必要がありました。しかし、そうするために私はクレイジーなハックなしでこれを行う必要がありました。これが私の解決策です。

他のいくつかのオブジェクト初期化子またはヘルパー関数。

this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(
  this.viewerContainer)
var viewer = this.viewer;
viewer.updateToolbarButtons =  this.updateToolbarButtons(viewer);

他のオブジェクトの関数。

updateToolbarButtons = function(viewer) {
  var _viewer = viewer;
  return function(width, height){ 
blah blah black sheep I can refer to this.anything();
}
};

0

すべての状況でdescribe機能するかどうかはわかりませんが、私たちの場合、Jestで関数をオーバーライドして、名前を解析し、describeいくつかの基準を満たしている場合はブロック全体をスキップできるようにしました。

これが私たちのために働いたものです:

function describe( name, callback ) {
  if ( name.includes( "skip" ) )
    return this.describe.skip( name, callback );
  else
    return this.describe( name, callback );
}

ここで重要な2つのこと:

  1. アロー関数は使用しません() =>

    矢印関数はへの参照を変更し、thisそれをファイルのにする必要がありますthis

  2. 使用this.describethis.describe.skipだけではなくdescribedescribe.skip

繰り返しになりますが、誰にとっても価値があるかどうかはわかりませんが、もともとはMatthew Crumleyの 優れた答えを回避しようとしましたが、メソッドを関数にして、条件付きで解析するためにparamsを受け入れる必要がありました。

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