• ベストアンサー
※ ChatGPTを利用し、要約された質問です(原文:コンストラクタすると同時にメソッドをオーバーロードするには?)

コンストラクタすると同時にメソッドをオーバーロードするには?

このQ&Aのポイント
  • コンストラクタすると同時にメソッドをオーバーロードする方法には、2つのアプローチがあります。
  • 方法(1)では、コンストラクタ後にメソッドを上書きすることで目的を達成します。
  • 方法(2)では、クロージャを使用してメソッドを生成することで目的を達成します。

質問者が選んだベストアンサー

  • ベストアンサー
回答No.9

> No.7お礼 >> 質問文中の方法(3)ですが(^^; >な…! >記憶にありません(汗) >多分、いろいろ書いているときにコピペでミスしてしまったんですね。 >この記述が誤解を与えたようで、すみませんでした。 Hello.prototype.start=function start(){} このfunction startという書き方について、「コピペでミスしてしまったんですね。」 と書かれていますよね。 ですが、No.8お礼ではあいかわらずfunction startと書かれていますが、function startなんでしょうか? > それでも、私が prototype を使用しているのは、start() の所在を明確にするため。 ふむ。。。 "start"がいっぱいあってstartが明確でない(どのstartがどのstartなのか)ので、変数名を変えてみました。 function Hello (value) { this.value = value ? value : 'Hello, World!'; this.start = (function(that, func){ return function (event) { func.call(that, event); }; })(this, this.bbb); } Hello.prototype.bbb = function aaa(event) { alert(this.value); }; var world1 = new Hello('Hello, World! (1)'); var world2 = new Hello('Hello, World! (2)'); document.getElementById('Test1').addEventListener('click', world1.start, false); document.getElementById('Test2').addEventListener('click', world2.start, false); これでalert(this.value)が期待通りになりそうですね。 なるほど、think49さんが書かれたthis.startの定義に渡される第二引数はHelloコンストラクタ内部で定義されるthis.startのことだと思っていましたが、どうやら違っていたようで。 start.call()で実行される関数もfunction startだと思っていたのですが、これも違ったようで、ちゃんと引数のstartになっていたようですね。 prototypeベースの作り方はよくわからないため、この方法が最も高速だとか読みやすいかどうかについては私からは言及できません。 再質問などでprototypeを熟知された方の回答を待たれる方が良いと思います。 最初にオーバーロードではなくオーバーライドの間違いではないか、と指摘しましたが、 オーバーロードでもオーバーライドでもないですね。 申し訳ありません。 改めて説明することではないと思いますが。 オーバーライド:同じ関数を書き直すこと var A=function(value){alert(value)} A=function (value, b){status=value;} A('hello')としてもA('hello', true)としても、どちらもalert(value)ではなくstats=valueが実行される。 オーバーライドすると、最初に定義した関数は消滅する。 オーバーロード:多重定義(JavaScriptにない概念、C++で記述(いろいろ省略、都合上C++の文法と異なった使い方あり)) void A(char* str){ cout << str << endl; // strを表示 } void A(char* str, bool f){   std::ofstream ofs( "file.txt" );   ofs << str << endl; // strをファイルに保存 } A('hello'); とすると画面にhelloが表示され、 A('hello', true); とすると、ファイルに保存されます。 これをJavaScriptで実現するには var A=function(value, b){ if(b) status=value; else alert(value); } A('hello'); // alert A('hello', true); // status という書き方をして、関数内で条件分岐させなければなりません。

think49
質問者

お礼

> ですが、No.8お礼ではあいかわらずfunction startと書かれていますが、function startなんでしょうか? う…、何度も申し訳ありません。 下記コードを書くつもりでまたコピペを…。(汗) Hello.prototype.start = function () { }; ---------------------------- > オーバーロードでもオーバーライドでもないですね。 質問当初は私も勘違いしていたのですが、私が想定していたのは「オーバーライド」でしたね。 その後、「prototype の原理からして、どちらでもない」ということに #6 の [お礼] を書いているときに気が付きました。 (ですので、いろいろと指摘いただいて助かりました。) 「オーバーロード」に関しては、用語は知りませんでしたが、 #2 で指摘いただいた arguments のキーワードと Wikipediaの「多重定義」から、実装方法の見当はつきました。 # 実は、以前、私はオーバーロードの実装法に関する質問をしており、「arguments.length を使う方法」「仮引数の値をチェックする方法」の2つを教わっていました。 # ただ…、方法は知っていても用語を知らなかったので、「オーバーロード」という曖昧に覚えていた言葉をそのまま使ってしまい…。(苦笑) 多重定義 http://ja.wikipedia.org/wiki/%E5%A4%9A%E9%87%8D%E5%AE%9A%E7%BE%A9 引数の数によって関数の動作を変えるには? | OKWave http://okwave.jp/qa/q5841966.html talooさんが例示されたコードは「仮引数が実引数より少ない時に、未指定の仮引数が undefined で初期化されるECMAScript仕様」を利用したものですね。 11.2.3 関数呼出し (Function Calls) - 11 式 (Expressions) http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/11_Expressions.html#section-11.2.3 13.2.1 [[Call]] - 13 関数定義 (Function Definition) http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/13_Function_Definition.html#section-13.2.1 10.2.3 関数コード (Function Code) - 10 実行コンテキスト (Execution Contexts) http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/10_Execution_Contexts.html#section-10.2.3 10.1.3 変数の実体化 (Variable Instantiation) - 10 実行コンテキスト (Execution Contexts) http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/10_Execution_Contexts.html#section-10.1.3 ……読むのに1時間ぐらいかかってしまいました。まだスラスラと読むには、ほど遠いです。 ---------------------------- 丁寧な回答をありがとうございました。 もう少し、時間をおいて、他に指摘or回答がつかなければ、質問を締め切りたいと思います。

その他の回答 (8)

回答No.8

> 多分、いろいろ書いているときにコピペでミスしてしまったんですね。 > この記述が誤解を与えたようで、すみませんでした。 不自然なところが何カ所か有るので、あまり気にしてなかったのですが、 そうなると、方法(3)はどういうのを作ろうとされてたんでしょうか? >> Hello.prototype.startをクロージャにするための無名関数(エンクロージャ)に渡すthis(の代わり)が何になるのかわかりません。m(_ _)m >方法(3) の解説ということでいいでしょうか。 そうです。 > 質問文 > ・方法(2)、方法(3) を比較すると、それぞれどのようなメリット、デメリットがあるのか。 > (prototypeが高速と聞いてからは (3) に傾いているのですが、よくわかっていません) prototypeを使用した作り方を考えられているそうなので、 function Hello(value){ this.value = value ? value : 'Hello, World!'; this.start=function(){} } ではなく function Hello(value){ this.value = value ? value : 'Hello, World!'; } Hello.prototype.start=function(){} で作り、かつworld.start()で実行する方法です。 まあ、インスタンスの拡張のタイミングをずらせば、worldを渡すことができますけどね。 function Hello (value) {  this.value = value ? value : 'Hello, World!'; } var world = new Hello('Hello, World2!'); // worldを生成してからHelloインスタンスを拡張する。 Hello.prototype.start = (function(that, start){   return function (event) {    start.call(that, event); //   that = start = null;   }; })( world, function(event){alert(this.value);} ); //生成済みのworldを渡す document.getElementById('Test').addEventListener('click', world.start, false); この方法では複数のオブジェクトを作ると動作がややこしくなります。 var world = new Hello('Hello, World2!'); var msg = new Hello('My Message!'); //Hello.prototype.start()の定義にworldを渡しているため、start()の内部のthisがmsgではなくworldを指すことになる。 document.getElementById('Test').addEventListener('click', world.start, false); document.getElementById('Test2').addEventListener('click', msg.start, false); Helloオブジェクトを1つしか作らないのであれば、Helloを拡張(Hello.prototypeを使用)し、worldを渡すようにしても良いかもしれません。 そうでなければHelloを拡張するのではなく、生成したオブジェクトを個々に拡張しないと行けないと思います。 function start(event){alert(this.value);} function warpper(that, func){   return function (event) {     func.call(that, event); //    that = func = null;   }; } function Hello (value) {   this.value = value ? value : 'Hello, World!'; } var world = new Hello('Hello, World2!'); world.start = warpper(world, start); var msg = new Hello('My Message!'); msg.start = warpper(msg, start);

think49
質問者

お礼

> そうなると、方法(3)はどういうのを作ろうとされてたんでしょうか? > ... > そうでなければHelloを拡張するのではなく、生成したオブジェクトを個々に拡張しないと行けないと思います。 うーん…。 方法(3) は生成したオブジェクトを個々に拡張していると思うのですが、私が何か勘違いしているのでしょうか…。 ----------- <p id="Test1">test1</p> <p id="Test2">test2</p> <script type="text/javascript"><!-- function Hello (value) {  this.value = value ? value : 'Hello, World!';  this.start = (function(that, start){   return function (event) {    start.call(that, event);   };  })(this, this.start); } Hello.prototype.start = function start (event) {  alert(this.value); }; var world1 = new Hello('Hello, World! (1)'); var world2 = new Hello('Hello, World! (2)'); document.getElementById('Test1').addEventListener('click', world1.start, false); document.getElementById('Test2').addEventListener('click', world2.start, false); //--></script> ----------- 私が 方法(3) でクロージャを使ってまで複雑な処理をしているのは、「仮引数の that が生成されたオブジェクトを参照し、クロージャによってその参照を維持すること」を期待するためです。 #7 でtalooさんは > ということは、方法(3)はこれでいいのでは?と思いますが。 と指摘されましたが、その方法も有りだと思います。 それでも、私が prototype を使用しているのは、start() の所在を明確にするため。 function Hello () {} Hello.start = function () {} function Hoge () {} Hoge.start = function () {} この状況で prototype を使わずに start() を定義する場合は HelloStart, HogeStart というように名前の衝突を回避しなければなりませんが、prototype を使えばメソッドの名前は短くてすみます。 方法(2) ではプライベート関数という形で start() の所在を明らかにしていますが、所在が異なるだけで、方法(2) と 方法(3) は全く同じ原理を使っています。 質問の意図を勘違いしていたら、すみません。 指摘は有り難いと思っていますので、遠慮なくご指摘下さい。

think49
質問者

補足

> var msg = new Hello('My Message!'); //Hello.prototype.start()の定義にworldを渡しているため、start()の内部のthisがmsgではなくworldを指すことになる。 多分ですが…、この部分で勘違いをされていると思います。 「this が参照するのは生成されたオブジェクトの方」で Hello 及び、Hello.prototype.start はずっと変化していません。 #6 の [お礼] でも少し触れましたが、これは上書きではなく、生成されたオブジェクトのプロパティを定義することで prototype の値より優先される事が狙いです。 prototypeを使う場合、String, Array などの標準オブジェクトを拡張する例がよく見られますが、 ------ String.prototype.trim = function () { return this.replace(/^\s|\s$/g, ''); }; var str = new String (' Hello '); str = str.trim(); alert('"' + str + '"'); ------ この場合の this も Stringオブジェクトを参照するのではなく、「new演算子で生成されたStringオブジェクト」(例示では str ) を参照しています。 prototype - MDC https://developer.mozilla.org/ja/Core_JavaScript_1.5_Reference/Global_Objects/String/prototype

回答No.7

> No.6お礼 > function start (event) { }; を代入する書き方は始めて見たのですが、よろしければ参考URLなど教えていただけないでしょうか? 質問文中の方法(3)ですが(^^; prototypeなしで検証コード。 var a=function b(arg){ alert(arg); }; この場合、以下と同じになるようです。 function b(arg){ alert(arg); }; var a=b; ---------- > No.6お礼 > 正確には、これは上書きではなく、obj.prop でプロパティ参照する際に「obj直属のプロパティから参照しにいくECMAScript仕様」を利用しています。 > (上書きではないので、Obj.prototype.prop も残っています) なるほど、下のような書き方をした場合、おおざっぱに言えば、こんな感じと理解させて頂きます。 function Hello(){ this.a=function(){}; // world.a()で実行できる } Hello.prototype.a=function start(){} // world.a()としては実行できないが、world.prototype.a()とするか、start()で実行できる world=new Hello(); ということは、方法(3)はこれでいいのでは?と思いますが。 function Hello (value) {  this.value = value ? value : 'Hello, World!';  this.start = (function(that, start){   return function (event) {    start.call(that, event);    that = start = null;   };  })(this, this.start); } function start (event) { // Hello.prototype.startへの代入をやめる  alert(this.value); }; var world = new Hello('Hello, World2!'); またはprototypeを使用して。。。と思ったのですが、 Hello.prototype.startをクロージャにするための無名関数(エンクロージャ)に渡すthis(の代わり)が何になるのかわかりません。m(_ _)m OOPはJavaから入っているため、prototypeベースの作り方はどちらかといえば苦手なのです。。。

think49
質問者

お礼

> 質問文中の方法(3)ですが(^^; な…! 記憶にありません(汗) 多分、いろいろ書いているときにコピペでミスしてしまったんですね。 この記述が誤解を与えたようで、すみませんでした。 > この場合、以下と同じになるようです。 早速、検証してみました。 -------------- (function () { // ケース(1) var a = function b (arg) { alert(arg); }; a ('Hello1'); // Hello1 b ('World1'); // (IE8 = "World1" / Firefox3.6, GoogleChrome5, Opera10.54 = "Uncaught ReferenceError: b is not defined") })(); (function () { // ケース(2) var a = function (arg) { alert(arg); }; a ('Hello2'); // Hello2 b ('World2'); // (IE8 = "オブジェクトを指定してください。" / Firefox3.6, GoogleChrome5, Opera10.54 = "Uncaught ReferenceError: b is not defined") })(); (function () { // ケース(3) var b = function (arg) { alert(arg); }; var a = b; a ('Hello3'); // Hello3 b ('World3'); // World3 })(); -------------- function b (arg) { alert(arg); }; で「オブジェクトb」としてが初期化されるのはIE8だけのようです。 クロスブラウザを考えると、この方法は使えないみたいですね…。 > Hello.prototype.startをクロージャにするための無名関数(エンクロージャ)に渡すthis(の代わり)が何になるのかわかりません。m(_ _)m 方法(3) の解説ということでいいでしょうか。 例えば、以下のようなコードを考えてみてください。 (function (str) { // 仮引数 alert (str); })('foo'); // 実引数 無名関数の仮引数は str であり、実引数は 'foo' です。 つまり、str に 'foo' が代入され、alert() に渡すことになります。 (function(that, start){ // 仮引数 (that === this, start === this.start)  return ... })(this, this.start); // 実引数 (this === world, this.start === Hello.prototype.start) 更に、functionオブジェクトを return する処理が入ります。 例えば、下記2つは同じ処理です。 ------ (function () { // クロージャの使用例(1) var a = function () { return function (str) { alert (str); }; // 関数オブジェクトを return する } var foo = a (); foo ('return1'); })(); (function () { // クロージャの使用例(2) var b = function (str) { alert (str); }; var a = function () { return b; // 関数オブジェクトを return する }; var foo = a (); foo ('return2'); })(); ------ 「関数a」は呼び出されることで関数オブジェクトを返し、その関数オブジェクトを実行することで alert(str); を内部コードに持つ関数が実行されます。 方法(3) では無名関数として既に実行されているので、return function () {}; がそのまま入るわけです。

回答No.6

字数制限により分割。m(_ _)m > NO.2 お礼 > この場合、this の参照先が p#Test に変化していることが分かります。 すみません。 No.3参照でお願いします。 > No.4 イベントリスナーを使うときに考慮すべきは、「thisは何を指すのか」だと思います。 私のselfを使う方法ではthis==HTML-Elementですが、 NO.2、No.3のcallを使った方法ではaddEventListenerに渡す関数でthis==worldになるように調整しています。 質問中のクロージャを使う方法ではthis.startまではthis==HTML-Elementですが、start.call()のところでthis==worldになるように調整していますから、 なるほど、それなら方法(2)もありかなあと思います。 > 方法(3) > Hello.prototype.start = function start (event) { >  alert(this.value); > }; じっくり読み直すと、 なるほど、クロージャのstart.call()はこの(右辺の)startのことか。。。 > this.start = (function(that, start){ > (略) >  })(this, this.start); これがprototype.startで上書きされないのは、 > Hello.prototype.start = function start (event) { > }; //上書きされない > Hello.prototype.start = function (event) { > }; //上書きされる ということなんですかねえ。。。 (prototypeにこういう仕様・違いがあることは聞いたことがないので、、、) > tagindex No.13 もう一度読み直すと、 > (function(){ // エンクロージャ > (略、function addEvent(){}等定義) > })(); という部分が書かれてましたね。 無名関数が終了するとaddEvent関数は消滅しますから、 その引数であるelement、listener変数(クロージャ=リークの原因として機能する)の保持が消える、 ということだと思います。 悪くはないと思うんですが、Ajaxなどで繰り返しaddEvent関数を使用したい場合に、 エンクロージャを動的に作る、というのは無理があるのではないか、、、と思います。 関数を消せば良いなら、、、 function AddEvent(){ this.addEvent=function(e,t, f,c){ if(e.addEvent) e.addEventListener(t, func, useCapture); else if(e.attachEvent) e.attachEvent('on'+t, func); }; } A=new AddEvent(); A.addEvent(element, 'click', func, false); delete A; // オブジェクトごとaddEvent関数を消去 というのをaddEvent関数を使いたい時に毎回やれば、addEventへの引数も消えるかもしれません。。。 一番簡単なのは、addEvent関数などのラッパーを作らずに毎回4行記述すればいいんですけどね。 if(element.addEvent) element.addEventListener(ev, func, useCapture); else if(element.attachEvent) element.attachEvent('on'+ev, func); > > 他の多くのサイトでは「メモリリークするパターン」として書かれています。 > 差し支えなければ、「他の多くのサイト」のURLを教えていただけないでしょうか? すみませんが、ブックマークなども削除しましたし、、、 http://p2b.jp/index.php?UID=1131336575 考え方はこのサイトの物と同じですが、無名関数(匿名関数)を経由させていたと思います。 どちらにしても結構複雑になると思います。

think49
質問者

お礼

ありがとうございます。 メモリリークの件、prototypeの件、いろいろ考えましたが、文字数制限の事もあるので、今回は質問内容に即した prototype に絞って書きたいと思います。 > > Hello.prototype.start = function start (event) { > > }; > //上書きされない function start (event) { }; を代入する書き方は始めて見たのですが、よろしければ参考URLなど教えていただけないでしょうか? 匿名関数を代入するのはわかりますが、名前付き関数を匿名関数っぽく代入できる、というところが今ひとつわかりません。 何度もすみません。お手数おかけしてます…。m(_ _)m --------------------- 私は「関数Helloは呼び出されるまで内部コードが実行されない」という認識でいます。 ですので、 --------- function Hello () {  // この中のコードは関数が呼び出されるまで実行されない  this.a = 'after';  alert (this.a); } Hello.prototype.a = 'before'; alert (Hello.a); // undefined (Hello が呼び出されていないため、this.a は未定義) alert (Hello.prototype.a); // before --------- このように、Hello.prototype.a が先に初期化されると思うのですが、いかがでしょうか? 上記コードに少し変更を加えれば、私が 方法(3) で world.start の挙動を変更したコードに近くなります。 --------- function Hello () {  alert (this.a); // before (this.a は存在しないので、this.prototype.a を参照する)  this.a = 'after'; } Hello.prototype.a = 'before'; var h = new Hello (); alert (h.a); // after --------- 正確には、これは上書きではなく、obj.prop でプロパティ参照する際に「obj直属のプロパティから参照しにいくECMAScript仕様」を利用しています。 (上書きではないので、Obj.prototype.prop も残っています) --------- function Obj () { } Obj.prototype.prop = 0; var obj = new Obj (); alert (obj.prop); // 0 (obj.prop への参照を試みるが、存在しないので Obj.prototype.prop を参照する) obj.prop = 1; // obj直属のプロパティとして初期化 alert (obj.prop); // 1 (obj.prop への参照を試み、存在するので obj.prop を参照する) alert (Obj.prototype.prop); // 0 --------- 11.2.1 プロパティアクセス演算子 - 11 式 (Expressions) http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/11_Expressions.html#section-11.2.1 > なるほど、クロージャのstart.call()はこの(右辺の)startのことか。。。 この際、引数として渡される this.start は Hello.prototype.start を参照しています。 this.start = function () {}; とすれば、world直属のプロパティとして this.start が初期化されるので、prototype.start よりも優先される、という仕組みです。 > すみませんが、ブックマークなども削除しましたし、、、 わかりました。 メモリリークに関しては、この先、そのURLを見る機会が訪れたときにまた一考したいと思います。

think49
質問者

補足

またまた、すみません。 [お礼] に書いたコードの説明が一点間違っていたので訂正します。m(_ _)m × alert (this.a); // before (this.a は存在しないので、this.prototype.a を参照する) ○ alert (this.a); // before (this.a は存在しないので、Hello.prototype.a を参照する)

回答No.5

OKWaveの場合は他の回答者からのツッコミがないというのが最大の欠点だと思いますけどね。 回答のミスに気づかないとそのままになりますから。 余談になるのかな、、、OOPとプライベート変数/関数の話です。 すみません、グローバル関数ではなくパブリック関数でした。m(_ _)m 言語によってはグローバルと呼ぶ物もあるかもしれませんが、、、 JAVAやC++などではプライベート/パブリックの概念を解説されているところが多いのですが、 JavaScriptのようなprototypeベースのOOP(後述のTest3)では、プライベート変数は使わないのが一般的だと思います。 function Test(){ var a=0; this.get=function(){ return a; }; this.set=function(val){ a=val; }; } var t=new Test(); t.set(1); alert(t.get()); // 1 alert(t.a); // undefined。this.aは未定義。var a(プライベート変数)にはアクセスできない t.a++; // error、未定義変数のインクリメントは出来ない function Test2(){ this.a=0; this.get=function(){ return this.a; }; this.set=function(val){ this.a=val; }; } var t2=new Test2(); t2.set(1); alert(t2.get()); // 1 alert(t2.a); // 1、this.aで定義してあるためアクセスできる(パブリック) t2.a++; // OK // prototypeベースで作る場合は、プロパティは内部に定義し、メソッドは後から追加することが多いと思います。 function Test3(){ this.a=0; } test3.prototype.get=function(){ this.a; }; test3.prototype.set=function(val){ this.a=val; }; var t3=new Test3(); t3.set(1); alert(t3.get()); // 1 alert(t3.a); // 1 Test2とTest3は、最終的には同じ構造になります。 メソッドやプロパティを一気に書き換える作り方もあります。(prototype.jsやYahooUI参照) (定義していない既存プロパティがあれば削除されます) Test.prototype={ a: 0, b: function(){}, c: function(){} };

think49
質問者

お礼

ありがとうございます。 OOPの考え方が非常に参考になります。 > this.get=function(){ return a; }; こちらを読んで、PHPにゲッターメソッド、セッターメソッドという概念があることを思い出しました。 手元のPHP独習を読むと、これらは総称してアクセサメソッドと呼び、以下のメリットが得られる、とあります。 -------- PHP独習(P106)より ・読み書きの制御が可能になる ・プロパティ値を設定する際に値の検証を行える ・プロパティ値を参照する際に値の加工を行える -------- 例えば、こんな事が可能だと思います。 -------- (function () {  function TestNode () {   var doc, node;   this.get = function () { return [doc, node]; }; // プロパティ値を参照する際に値を配列に加工する   this.set = function (doc1, node1) {    if (doc1.nodeType !== 9) { throw TypeError (doc1 + ' is not a DOCUMENT_NODE'); return false; } // プロパティ値を設定する際に nodeType の検証を行う    if (!node1.nodeType) { throw TypeError (node1 + ' is not a Node'); return false; } // 同上    doc = doc1;    node = node1;   };  }  var testNode = new TestNode ();  testNode.set (document, document.getElementsByTagName('p').item(0));  var nodes = testNode.get ();  alert (nodes[0]); // [object HTMLDocument]  alert (nodes[1]); // [object HTMLHtmlElement] })(); -------- これは値の検証、加工を行えることも一つのメリットですが、メモリリークを回避できるケースもあるように思いました。 -------- // ここで function TestNode () { } が宣言されたものとする var testNode = new TestNode (); testNode.set (document, document.getElementsByTagName('p').item(0)); (testNode.get ())[0].onclick = function () {  // このクロージャは testNode への参照を保持するが、testNode.get() を実行しなければDOMノードを参照しないので、循環参照とはならない }; -------- > すみません、グローバル関数ではなくパブリック関数でした。m(_ _)m 完全ではありませんが、理解しました。 どうやらクラスの概念から来ているようですが、JavaScript言語における正式な用語ではないようですね。 考え方は役立つので、概念は理解しておこうと思います。 public … クラスにおいて、外部に公開されている事を意味する (public関数, public変数) private … クラスにおいて、外部に公開されていない事を意味する (private関数, private変数) > メソッドやプロパティを一気に書き換える作り方もあります。 最近、私も同じような書き方を使うようにしています。 var _a = function () {}, _b = function () {}; Test.prototype = { a: _a, b: _b }; ・prototypeで拡張したプロパティが一目で分かる ・コンストラクタ関数の名前変更時に書き換える部分が2箇所で済む という点で気に入っています。

回答No.4

私自身、クロージャの使用でなぜメモリリークが発生し、なぜメモリリークが発生しないのかが未だにわかっていません。 また、ブラウザのバージョン違いで全く違う現象が起きますし、 No.1お礼のtagindexのサイト、No.13のAddEventはtagindexの投稿ではメモリリークしないと書かれていますが、 他の多くのサイトでは「メモリリークするパターン」として書かれています。 (おそらくHTML-Elementを引数として渡すことがメモリリークの原因だと思います) tagindexの回答者の言い方が好きではないので行かないのですが、、、 > オブジェクトの管理者が同じなら、循環参照しても問題ありません。 htmlElement.parentNode.firstChild.parentNodeの循環がなぜメモリリークしないのかという謎がありましたが、 それが本当ならまあ、本当なんでしょう。 そうであれば、NO.3のselfを削除する必要はないはずです。 No.2を書いていたときは、方法(2)や方法(3)でなぜクロージャなんだろうという疑問がありましたが、 No.3に書いたようなthisの参照をworldに向けるためということですよね。 このクロージャも同様に、メモリリークしないパターンに該当するのではないでしょうか。

think49
質問者

お礼

何度も回答ありがとうございます。 メモリリークの件も別の視点から考えると、また参考になります。 > 他の多くのサイトでは「メモリリークするパターン」として書かれています。 差し支えなければ、「他の多くのサイト」のURLを教えていただけないでしょうか? 例えば、私が探したところでは、以下が見つかりました。 IEのメモリリーク問題 http://p2b.jp/index.php?UID=1131336575 ここでは「addEvent() の中で匿名関数を使っていることが問題」という趣旨で指摘されており、2つ目の addEvent() では匿名関数を外に追い出す事でメモリリークを解消。 とりあえず、私の認識とは一致しています。 指摘のあった No.13 ですが、こちらも匿名関数を addEvent() 内で定義しないことでメモリリークを回避しています。 http://www.tagindex.com/cgi-lib/q4bbs/patio.cgi?mode=view2&f=2586&no=13 しかし、他に多くのサイトがあるのなら、私が誤解しているのかもしれませんので、教えていただけると嬉しいです。(複雑な問題ですので…。) > tagindexの回答者の言い方が好きではないので行かないのですが、、、 手厳しいですね。(汗) 私も気をつけます。 > そうであれば、NO.3のselfを削除する必要はないはずです。 > > No.2を書いていたときは、方法(2)や方法(3)でなぜクロージャなんだろうという疑問がありましたが、 > No.3に書いたようなthisの参照をworldに向けるためということですよね。 > このクロージャも同様に、メモリリークしないパターンに該当するのではないでしょうか。 #3 は window.onunload 時にクロージャが参照するローカル変数を全て削除しているので、いずれにせよメモリリークしないパターンになると思います。 私が恐れるのは、#1 の [お礼] に書いたような、「this でDOMノードを参照できて、onunload時の処理がない状況」です。 その場合、this を self や that などのローカル変数にしてしまうと、クロージャが参照を持ち続ける条件が揃ったときに循環参照してしまいます。 「onunload時に参照を切れば済む」というのは確かにそうなんですが、手間と効率の兼ね合いで省略したい、と思うときがありまして…。(汗) #3 のお礼でも触れましたが、Hello() 側で listener を出力するのが一つの境目のように感じてきました。 いっそのこと、Hello() 側でイベント定義するのは諦めて、別のオブジェクトにイベント定義用メソッドをまとめた方が合理的だったのかもしれません。

think49
質問者

補足

すみません、書き間違えました。 以下のように訂正します。m(_ _)m × 私が恐れるのは、#1 の [お礼] に書いたような、「this でDOMノードを参照できて、onunload時の処理がない状況」です。 ○ 私が恐れるのは、#1 の [お礼] に書いたような、「ローカル変数でDOMノードを参照できて、onunload時の処理がない状況」です。

回答No.3

こうしないとthis==worldにならないのでダメかも。 document.getElementById('Test').addEventListener('click', function(){world.start.call(world);}, false); *机上デバッグより 私のやり方ですが、だいたい自分でnull代入かdeleteで消しています。 実際の所、クロージャ(self)の初期化タイミングの違いだけだと思いますが。 function Hello(val){ var self=this; var value=val; this.start=function(){alert(self.value);}; this.destroy=function(){self=value=null;}; } var world=new Hello('hello world!'); world.start(); window.onunload=world.destroy; どこまでメモリリーク対策になっているかはわかりません。 スクリプトを使わなくてもメモリリークしてるブラウザは多いですから。

think49
質問者

お礼

> document.getElementById('Test').addEventListener('click', function(){world.start.call(world);}, false); callメソッドを利用するところが 方法(1) とよく似ていますね。 その後、余所で教えていただいた方法でも、addEventListenerの宣言部分で this の参照先を変更していました。 <!-- 方法(1) の派生 / コンストラクタした後にメソッドを上書きする --> <p id="Test">test</p> <script type="text/javascript"><!-- function Hello (value){ this.value = value ? value : 'Hello, World!'; } Hello.prototype.start = function (event) { alert(this.value); } var world = new Hello(); document.getElementById('Test').addEventListener('click', { // listenerでオブジェクトを渡すと、this の参照先が渡したオブジェクトに変化する  value    : world.value,  handleEvent : world.start }, false); //--></script> インタフェース EventListener (DOM水準2で導入) - Document Object Model Events http://www.y-adagio.com/public/standards/tr_dom2_events/events.html#Events-EventListener やはり、addEventListener宣言時に this の参照先を変更するのが一般的なようです。 全てをメソッドを Hello () に含めようとしたところに、無理があったのだと感じました。 > スクリプトを使わなくてもメモリリークしてるブラウザは多いですから。 確かに…。 私は最近までFirefox3.6を使っていましたが、長時間立ち上げ続けているとタブを閉じてもメモリが解放されない不具合に悩まされました。 Google Chrome5 (乗り換えたのは4.1から) に乗り換えてからは、メモリ不足に悩まされることはなくなりました。 # 最も、マルチプロセスであるGoogle Chrome5 と Firefox3.6 を比較するのはフェアじゃないかもしれませんが…。

回答No.2

方法(2)はプライベート関数を呼び出すグローバル関数(メソッド)を作ってるだけで、特にクロージャにする必要はないと思います。 this.start = function(event){    start.call(this, event); } 方法(3)のクロージャの部分は、 その後のHello.prototype.start=function start(event){}で上書きされているため、これも意味がない気がします。 たぶんこれと同じではないでしょうか。 function Hello (value) {  this.value = value ? value : 'Hello, World!'; } Hello.prototype.start = function(event) { //function start(event)でも可  alert(this.value); }; もっとスリムにするならプライベート関数のstartが要らないんじゃないかと、、、 function Hello (value) {  this.value = value ? value : 'Hello, World!';  this.start = function(event){   alert(this.value);  }; } あと、オーバーロードではなくオーバーライドです。 (JavaScriptではオーバーロードできません。関数内でargumentsから条件分岐する必要があります。)

think49
質問者

お礼

> 方法(3)のクロージャの部分は、 > その後のHello.prototype.start=function start(event){}で上書きされているため、これも意味がない気がします。 function Hello (value) { } の内部コードは「コンストラクトする時に実行される」と認識しています。 ご指摘のコードですが、以下のコードで実験してみてください。 <p id="Test">test</p> <script type="text/javascript"><!-- function Hello (value) { this.value = value ? value : 'Hello, World!'; } Hello.prototype.start = function(event) { //function start(event)でも可 alert(this.value); // undefined alert(this); // [object HTMLParagraphElement] }; var world = new Hello (); document.getElementById('Test').addEventListener('click', world.start, false); //--></script> この場合、this の参照先が p#Test に変化していることが分かります。 イベントリスナでは通常、リスナー関数の中で this が参照するのは event.target と同じですが、それでは困るので 方法(3) では callメソッドを使用して this の参照先を変更しています。 > あと、オーバーロードではなくオーバーライドです。 失礼しました。 「オーバーロード = 多重定義(後付の定義が優先される)」「オーバーライド = 上書き」なのですね。 argumentsでオーバーロードと同様の動作を実現する件も把握しました。

think49
質問者

補足

多数の回答ありがとうございます。 お礼が遅れてすみません。 prototypeについてはまだ覚えて間もないため、オライリー本を熟読して間違いのないレスをしようとして時間がかかってしまいました。 > 方法(2)はプライベート関数を呼び出すグローバル関数(メソッド)を作ってるだけで、特にクロージャにする必要はないと思います。 グローバル関数という用語は知らなかったので調べてみました。 グローバルスコープで宣言された関数オブジェクトのことなのですね。 良い機会なのでprototypeの用語についても調べました。 プロトタイプ(prototype)によるJavaScriptのオブジェクト指向:CodeZine http://codezine.jp/article/detail/222 JavaScript - コンストラクタ関数 | Diaspar Journal http://diaspar.jp/node/94 ご指摘の件ですが、this が window を参照しており、window.start = function (event) {} の宣言が行われているのではないか、という指摘と受け止めました。 確かに通常の関数内での this は window を参照するのですが、new演算子でオブジェクトを生成するとコンストラクタ関数内の this は生成されたオブジェクトを参照するように思います。 方法(2) においての this は new Hello() で生成した「worldオブジェクト」を参照する、という認識でいます。 仮に this が window を参照しているならば、start で参照可能だと思いますが、 <!-- 方法(2) クロージャでメソッドを生成する --> <p id="Test">test</p> <script type="text/javascript"><!-- function Hello (value) {  function start (event) {   alert(this.value);  }  this.value = value ? value : 'Hello, World!';  this.start = (function(that){ // thisはコンストラクタした変数(world)を参照する   return function (event) {    start.call(that, event);    that = null;   };  })(this); } var world = new Hello('Hello, World2!'); console.log(world.start); // startはworldのプロパティとなる console.log(start); // Uncaught ReferenceError: start is not defined (グローバル空間にstartが存在しないので、ReferenceError に) document.getElementById('Test').addEventListener('click', world.start, false); //--></script> このように参照できません…。

回答No.1

thisに依存しないコードを書くのもありでしょうか? (function(){ var Hello = (function(){ var that = {}; var value; var start = function(){ alert(value); return that; }; that.start = start; var greeting = function(msg){ if(msg){ value = msg; return that; }else{ return value; } }; that.greeting = greeting; return function(msg){ greeting(msg || 'Hello, World!'); return that; }; })(); var world = Hello('Hello, World2!'); document.getElementById('Test').addEventListener('click', world.start, false); })();

think49
質問者

お礼

アドバイスありがとうございます。 > thisに依存しないコードを書くのもありでしょうか? クロージャで function を生成して返し、値を保持するのですね。 それも解決策の一つだと思います。 ただ…、本音をいいますと「出来ればクロージャを使いたくない」のです…。 後出しですみません。質問文では「クロージャを使わずに解決できるのか」とは書きましたが、理由については文字数制限の都合上削ってしまいました。 今、[お礼] という枠が出来ましたので、説明したいと思います。 Internet Explorer リーク パターンを理解して解決する http://msdn.microsoft.com/ja-jp/library/bb250448%28VS.85%29.aspx 掲示板/クロージャでメモリリークするパターン - TAG index http://www.tagindex.com/cgi-lib/q4bbs/patio.cgi?mode=view2&f=2586&no=8- IE6のメモリリークパターンはクロージャを使った事例が多く、クロージャを使わなければリークパターンを考える手間がかなり省けます。 prototypeはクロージャを考慮しなくてすむ点で有り難い存在なのですが、startメソッドを上書きする事を考えると私にはクロージャを使う方法しか思いつきませんでした…。 <p id="Test">test</p> <script type="text/javascript"><!-- function Hello (value) {  this.value = value ? value : 'Hello, World!';  this.doc  = document;  this.start = (function(that, start){   return function (event) { // このクロージャはローカル変数thatへの参照を保持する (従って、that.doc というDOMノードへの参照も保持する)    start.call(that, event);   };  })(this, this.start); } Hello.prototype.start = function start (event) {  alert(this.value); }; var world = new Hello('Hello, World2!'); document.getElementById('Test').addEventListener('click', world.start, false); //--></script> この場合、リスナー関数はローカル変数thatを通して、docを参照し続けるので循環参照してしまいます。 これが this.doc という形で参照するなら、「this はローカル変数ではないので循環参照しないだろう」という思惑がありました。 しかし、クロージャを生成する時点でローカル変数thatの参照を切れない(質問文ではnullで埋めましたが、これでは二回目以降のイベントが発動しないという問題がありました)ので、結局は循環参照してしまいます。 この場合の解決法は、今思いつく限りでは4つあります。 ・グローバル変数でDOMノードを保持する ・this でDOMノードを保持しない (必要なら関数実行時に引数で渡す。(例) world.start(document); ) ・リスナー関数で必要な値は引数で渡し、this参照しない ・window unload時に全てのイベントを削除する unload時の処理は正攻法でどんな場合にも適用できるのですが、手間と効率の兼ね合いで省略する事もあります。 ここでもう一つ、新しい回避手段が見いだせればと思い、質問に至りました。 ただ…、改めて考えてみると this.doc = document; を宣言するならば、方法(1) でもリークしてしまいます。 「クロージャを使わない方法を」と考え続けてきましたが、「考える上での原点がずれていたのかもしれない」とも思い始めています…。

関連するQ&A