更新:
私はあなたのフィドルを詳しく調べました。いくつかの修正が必要でした。コンストラクター内ですべてのメソッドを定義しています。インスタンスを作成するたびに、同じ関数を何度も作成することになります。それは完全にやり過ぎです。これは、すべてのメソッドがプロトタイプのプロパティである実用的なフィドルです。
特に、いくつかの問題を引き起こしている 1 つの行がありました。
$(this).animate({width:200}, getRandomInt(500, 3000), engine.oncomplete);
ここでは、コールバックとしてメソッドへの参照oncomplete
を渡していますが、関数オブジェクトが呼び出されるコンテキストはad-hocと判断されるため、これはもはやインスタンスを参照していませんでしたengine
が、それは簡単な修正です:ラッパー関数:
$(this).animate({width:200}, getRandomInt(500, 3000), function()
{//this anonymous function will get some context
engine.oncomplete();//but invokes oncomplete in the context of engine, this references engine here
});
var self
別のアプローチは次のようになります:プロトタイプ メソッドをオーバーライドするクロージャ (あなたのように、おそらく無意識のうちに で作成されたもの):
engine.oncomplete = (function(that,prototypeMethod)
{//argument is reference to engine object, and the original method, that we're overriding
{
return function()
{//new version of the method, you can do this right after the instance is created
//or even in the constructor
return prototypeMethod.apply(that,[]);//call in correct context
};
}(engine,engine.oncomplete));
$(this).animate({width:200}, getRandomInt(500, 3000),engine.oncomplete);
delete engine.oncomplete;//reset to prototype method, optional
いずれにせよ、それだけです。この問題に関する詳細情報は次のとおりです-私は思います.
まだ微調整できる点がいくつかありますが、全体としては順調に進んでいると思います。覚えておいてください: メソッドは、コンストラクターから十分に離しておくのが最善です。2番目のオプションが少しあいまいな場合(IMOです)。これが、この回答に追加する最後のリンクになることを約束しました。JavaScript のメモリ リーク
についてしばらく前に質問しました。これは私の巨大な投稿の 1 つですが、コード スニペットの一部: 関数がどのように呼び出されるかを見てください。また、私が自分の質問に投稿した「回答」も見てください。どのようにネイティブであるかの例があります。reusableCallback
prototypes
に使える。退屈したり、興味がある場合は、コードを読んthis
で、いつでも何が参照されるかを考えてみてください。それを解決できれば、JS がスコープを決定する基本原則を知っていると言っても過言ではありません。
には というプロパティがないthis.finish
ため、 と書くことはできません。変更するだけです:this
finish
Engine.finish = function(){};
に
Engine.prototype.finish = function(){};
なんで?JS関数はファーストクラスのオブジェクトだからです(変数に割り当てることができ、とにかく関数オブジェクトへの参照が可能であり、引数として関数に渡すことができ、関数の戻り値にすることができます...
)試してみてくださいconsole.log(Engine instanceof Object);
、それはtrueをログに記録します。だからあなたがやっていることはこれです:
function Engine(){};
//is hoisted, and interpreted as:
var Engine = function(){};//<-- a function object, that is referenced by a var: Engine
Engine.finish = function(){};
//just like any object, assign a property to the object that Engine is referencing
ただし、新しい Engine オブジェクトを作成すると、次のようになります。
var myEngine = new Engine();
JS は、参照しているオブジェクトのプロトタイプをチェックしEngine
ます。この場合は、関数を参照しています。関数はnew
キーワードを使用して呼び出されるため、JS はオブジェクトを作成します。このためには、プロトタイプ (およびそれらの多く) が必要です。この特定の関数 (コンストラクター) のプロトタイプにはプロパティが定義されていないため、JS はチェーン内の次のプロトタイプであるプロトタイプを探しますObject
。そのプロトタイプのすべてのメソッドとプロパティが新しいインスタンスに渡されます ( と を試してみるmyEngine.toString();
とObject.prototype.toString === myEngine.toString
、同じメソッドを共有していることがわかります)。
これは完全に真実/正確ではなく、コンストラクターから新しいオブジェクトを作成するときに行われるすべてのことの 10% でさえないことに注意してください。 :
すべての新しいインスタンスにfinish
メソッドが必要で、ご存じのとおり、すべての新しい Engine インスタンスが にあるメソッドを継承するとします。次のObject.prototype
ようにします。
Object.prototype.finish = function(){};//!!
確かにうまくいきますが、これもうまくいきますsomeArray.finish
。はい、配列はオブジェクトであり、関数、DOM 要素への参照、およびあなたが持っているものと同様です。オブジェクトのプロトタイプを変更しないでください。
代わりに、JS では、特定のコンストラクターのすべてのインスタンスで共有されるプロトタイプ レベルで特定のメソッドを定義できます。
function Engine(){};//emtpy
Engine.prototype.i = 0;
var newEngine = new Engine();
console.log(newEngine.i);//0
var anotherEngine = newEngine();
newEngine.i = 1234;
console.log(anotherEngine.i);//0
console.log(newEngine.i);//1234
これにより、次のような基本的なプロトタイプ チェーンが作成されます。
EngineInstance {list of instance properties}
||
====>Engine.prototype {list of properties that all Engine instances have}
||
====> Object.prototype {properties for all objects, Engine is an object, so it should have these, too}
したがって、新しいインスタンスが作成されるたびに、その新しいインスタンスには、プロトタイプで定義されているプロパティが割り当てられます。結論として、メソッドは (インスタンスごとに新しい関数を作成するコンストラクター内ではなく) 1 回作成することができ、すべてのインスタンスがそのメソッドを共有します。詳細については MDN を参照してください
1 つだけ: JS は、オブジェクトが作成されるのと同じ方法でオブジェクトのプロパティにアクセスしますが、順序が逆になります:
を呼び出すnewEngine.finish
と、JS は最初にそのインスタンスに独自のfinish
プロパティがあるかどうかを確認し、そうでない場合はEngine
プロトタイプを調べます。 、それにも終了メソッドがない場合、JS は、目的のプロパティが見つかるまで ( までObject.prototype
)、プロトタイプ チェーンを続行します。または、オブジェクト プロトタイプにプロパティがない場合は、それは単に を返すだけです。undefined
これは、本当にすべてを物語っています。このプロパティはundefined
です。
この結果として、デフォルトの動作とは異なる動作をさせる必要がある限り、プロトタイプのメソッド/プロパティをマスクdelete
し、その後は単純に変更されたメソッドをマスクすることができます。実際の例を次に示します。
function doAjax(data, url, method)
{
var xhr = makeAjaxInstance();
data.toString = function()
{
return JSON.stringify(this);
};
//some more stuff, but then:
xhr.send(data + '');//<-- add empty string forces implicit call to the toString method
delete data.toString;
}
doAjax('my/url',{foo:'bar'},'post');
JS は通常のルーチンを実行しますtoString
。メソッドのインスタンスをチェックし、それを見つけます。関数が呼び出され、適切な JSON 文字列が送信されます。その後、toString
メソッドは削除されます。
JS は、インスタンス自体でそのプロパティを検索します。メソッドが削除されるため、プロトタイプの toString メソッドはマスクされなくなります。次に呼び出されると、JS は同じトリックを実行しますが、メソッドを見つける代わりにdata
、プロトタイプをスキャンし、通常のサービスが再開されます。
この答えが混沌としすぎていないことを願っていますが、私は非常に疲れているので、そうであれば: ごめんなさい