JavaScript:関数を拡張する


93

それが欲しい主な理由は、自分の初期化関数を拡張したいからです。

このようなもの:

// main.js

window.onload = init();
function init(){
     doSomething();
}

// extend.js

function extends init(){
    doSomethingHereToo();
}

それで、PHPでクラスを拡張するように関数を拡張したいと思います。

また、他のファイルからも拡張したいと考えています。たとえば、元のinit関数main.jsとの拡張関数がありextended.jsます。



回答:


102

あなたが実際に何をしようとしているのか、そしてあなたがそれをしている状況の広い視野で、私たちはあなたの質問に対する文字通りの答えよりも良い答えを与えることができると私は確信しています。

しかし、ここに文字通りの答えがあります:

これらの関数をどこかのプロパティに割り当てる場合は、元の関数をラップして、代わりにプロパティに置き換えることができます。

// Original code in main.js
var theProperty = init;

function init(){
     doSomething();
}

// Extending it by replacing and wrapping, in extended.js
theProperty = (function(old) {
    function extendsInit() {
        old();
        doSomething();
    }

    return extendsInit;
})(theProperty);

関数がまだオブジェクト上にない場合は、上記を容易にするために関数をそこに配置することをお勧めします。例えば:

// In main.js
var MyLibrary = (function() {
    var publicSymbols = {};

    publicSymbols.init = init;
    function init() {
    }

    return publicSymbols;
})();

// In extended.js
(function() {
    var oldInit = MyLibrary.init;
    MyLibrary.init = extendedInit;
    function extendedInit() {
        oldInit.apply(MyLibrary); // Use #apply in case `init` uses `this`
        doSomething();
    }
})();

しかし、そこにあるようにそれを行うには良い方法は。たとえば、init関数を登録する手段を提供します。

// In main.js
var MyLibrary = (function() {
    var publicSymbols = {},
        initfunctions = [];

    publicSymbols.init = init;
    function init() {
        var funcs = initFunctions;

        initFunctions = undefined;

        for (index = 0; index < funcs.length; ++index) {
            try { funcs[index](); } catch (e) { }
        }
    }

    publicSymbols.addInitFunction = addInitFunction;
    function addInitFunction(f) {
        if (initFunctions) {
            // Init hasn't run yet, rememeber it
            initFunctions.push(f);
        }
        else {
            // `init` has already run, call it almost immediately
            // but *asynchronously* (so the caller never sees the
            // call synchronously)
            setTimeout(f, 0);
        }
    }

    return publicSymbols;
})();

(上記の多くはもう少しコンパクトに書くことができますが、publicSymbols私は通常のpubsまたは匿名のオブジェクトリテラルではなく、明確な名前を使用したいと思いました。匿名関数が必要な場合は、もっとコンパクトに書くことができますが、私はしません匿名関数を大いに注意してください。)


すばらしい答えをありがとう。2番目の例の私の問題は、拡張している関数の結果が必要になる可能性があることです。
Gerhard Davids

64

これにはいくつかの方法がありますが、それはあなたの目的が何であるかによって異なります。同じように関数を実行したいだけの場合は、次のように使用できます.apply()

function init(){
  doSomething();
}
function myFunc(){
  init.apply(this, arguments);
  doSomethingHereToo();
}

新しいinitで置き換える場合は、次のようになります。

function init(){
  doSomething();
}
//anytime later
var old_init = init;
init = function() {
  old_init.apply(this, arguments);
  doSomethingHereToo();
};

2
場合によっては、の.call代わりにメソッドが必要になることがあります.applyこの StackOverflowの質問を参照してください。
MrDanA 2014年

@ニック、既存の関数を拡張するためのJavaScriptの例は非常に便利だと思いましたが、jQueryを使用してこれと同じことを行う方法に興味がありましたか?
Sunil、2014年

+1ありがとうございます。これは、元のjsを変更せずにサードパーティのプラグインにパッチを適用する場合に非常に便利です。
GFoley83 2015

1
パラメータと戻り値を期待する関数でそれを使用する方法がわかりません。
ゲルフリート2017

5

他のメソッドは素晴らしいですが、それらはinitにアタッチされたプロトタイプ関数を保持しません。それを回避するには、次のようにします(Nick Craverからの投稿に触発されました)。

(function () {
    var old_prototype = init.prototype;
    var old_init = init;
    init = function () {
        old_init.apply(this, arguments);
        // Do something extra
    };
    init.prototype = old_prototype;
}) ();

4

別のオプションは次のとおりです。

var initial = function() {
    console.log( 'initial function!' );
}

var iWantToExecuteThisOneToo = function () {
    console.log( 'the other function that i wanted to execute!' );
}

function extendFunction( oldOne, newOne ) {
    return (function() {
        oldOne();
        newOne();
    })();
}

var extendedFunction = extendFunction( initial, iWantToExecuteThisOneToo );

0

これは非常にシンプルで簡単です。コードを見てください。JavaScript拡張機能の背後にある基本概念を理解してみてください。

まず、JavaScript関数を拡張します。

function Base(props) {
    const _props = props
    this.getProps = () => _props

    // We can make method private by not binding it to this object. 
    // Hence it is not exposed when we return this.
    const privateMethod = () => "do internal stuff" 

    return this
}

次の方法で子関数を作成することにより、この関数を拡張できます

function Child(props) {
    const parent = Base(props)
    this.getMessage = () => `Message is ${parent.getProps()}`;

    // You can remove the line below to extend as in private inheritance, 
    // not exposing parent function properties and method.
    this.prototype = parent
    return this
}

これで、Child関数を次のように使用できます。

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

このようにJavaScriptクラスを拡張することで、JavaScript関数を作成することもできます。

class BaseClass {
    constructor(props) {
        this.props = props
        // You can remove the line below to make getProps method private. 
        // As it will not be binded to this, but let it be
        this.getProps = this.getProps.bind(this)
    }

    getProps() {
        return this.props
    }
}

このクラスを次のようなChild関数で拡張してみましょう。

function Child(props) {
    let parent = new BaseClass(props)
    const getMessage = () => `Message is ${parent.getProps()}`;
    return { ...parent, getMessage} // I have used spread operator. 
}

ここでも同様に、Child関数を次のように使用して同様の結果を得ることができます。

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

JavaScriptは非常に簡単な言語です。ほとんど何でもできます。JavaScriptをご利用いただきありがとうございます。あなたのケースで使用するアイデアを提供できたと思います。


-1

extendFunction.jsを使用する

init = extendFunction(init, function(args) {
  doSomethingHereToo();
});

ただし、特定のケースでは、グローバルonload関数を拡張する方が簡単です。

extendFunction('onload', function(args) {
  doSomethingHereToo();
});

私は実際にあなたの質問が本当に好きです、それは私にさまざまなユースケースについて考えさせています。

JavaScriptイベントの場合、本当にハンドラを追加および削除したいのですが、extendFunctionの場合、後で機能を削除するにはどうすればよいでしょうか。拡張関数に.revertメソッドを簡単に追加できるためinit = init.revert()、元の関数が返されます。明らかに、これはかなり悪いコードにつながる可能性がありますが、おそらくコードベースの外部の部分に触れることなく何かを成し遂げることができます。

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