以下のjavascript関数について良いパターンを教えてください。
関数では、ユーザ操作等のイベントに応じて、インスタンス内のメソッドでdataを更新し、表示を切り替えようとしています。
data及び表示の管理をインスタンスのみにさせ、ユーザ操作等のイベントをインスタンスに対して伝える仕様にできれば綺麗かと考えているのですが、
実際には下記はthisの仕様によりエラーとなります。
(ユーザ操作等のイベントの場合イベント発生元がthisになるので)
単にエラーを回避して動かすことはできるのですが、
出来るだけ良いプログラム構造としたいです。
※クラス・インスタンスは複数種類存在します(SampleClassA、SampleClassB…、sampleA1、sampleA2、sampleB1...)
javascript
1//呼び出し側の記載/////////// 2var sampleA1 = new SampleClassA(); 3//初期イベント 4sampleA1.initialEvent(); 5//windowサイズ変更時のイベント等 6$(window).on('resize', sampleA1.resizeEvent) 7 8 9//SampleClassA/////////// 10function SampleClassA() { 11 this.data; 12 this.object; 13} 14SampleClass.prototype = { 15 setDefaultData: function() { 16 this.data = { 17 pointA: 10, 18 pointB: 10, 19 pointC: 10, 20 pointD: 10, 21 }; 22 return true; 23 }, 24 doDisplay: function() { 25 this.object = new SampleLibrary.display(this.data); 26 return true; 27 }, 28 initialEvent: function() { 29 this.setDefaultData; 30 this.doDisplay; 31 return true; 32 }, 33 updateResizeData: function() { 34 this.data.pointA = this.data.pointA++; 35 this.data.pointC = this.data.pointC++; 36 return true; 37 }, 38 resizeEvent: function() { 39 this.updateResizeData; //エラー!!! 40 this.doDisplay; 41 return true; 42 }, 43}
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答6件
0
前提
おそらく、ご掲示のコードはテストしてないのではないでしょうか。
sampleA1.initialEvent()
の行で TypeError
になります。
JavaScript
1sampleA1.initialEvent(); // TypeError: sampleA1.initialEvent is not a function
また、様々な部分で関数呼び出しされていないため、正しく実行されません。
以上のコードを修正したとして下記コードになると思われます。
(地味に手間なのでサンプルでも動く事を確認してから掲載するようにしていただけると助かります)
JavaScript
1'use strict'; 2function SampleClassA () { ; } 3 4SampleClassA.prototype = { // (修正) SampleClassAに修正 5 setDefaultData: function setDefaultData () { 6 this.data = { 7 pointA: 10, 8 pointB: 10, 9 pointC: 10, 10 pointD: 10 11 }; 12 }, 13 doDisplay: function doDisplay (/* [window] */) { 14// this.object = new SampleLibrary.display(this.data); // (修正) ReferenceError の為、コメントアウト 15 16 if (arguments.length > 0) { 17 var window = arguments[0]; 18 console.log(window.innerWidth, window.innerHeight); 19 } 20 console.log(this.data); 21 }, 22 initialEvent: function initialEvent () { 23 this.setDefaultData(); // (修正) 関数呼び出しする 24 this.doDisplay(); // (修正) 関数呼び出しする 25 }, 26 updateResizeData: function updateResizeData () { 27 var data = this.data; 28 ++data.pointA; // (修正) インクリメントされていなかった不具合を修正 29 ++data.pointC; // (修正) インクリメントされていなかった不具合を修正 30 }, 31 resizeEvent: function resizeEvent (event) { 32 console.log(event); 33 this.updateResizeData(); // TypeError: this.updateResizeData is not a function (修正) 関数呼び出しする 34 this.doDisplay(event.target); // (修正) 関数呼び出しする 35 } 36}; 37 38SampleClassA.prototype.initialEvent = function initialEvent () { 39 this.resizeEvent = this.resizeEvent.bind(this); 40 this.setDefaultData(); // (修正) 関数呼び出しする 41 this.doDisplay(); // (修正) 関数呼び出しする 42} 43 44// (修正) 呼び出しコードを後ろに持ってくる 45var sampleA1 = new SampleClassA(); 46//初期イベント 47sampleA1.initialEvent(); 48//windowサイズ変更時のイベント等 49jQuery(window).on('resize', sampleA1.resizeEvent);
解決法
(方法1) addEventListener
の handleEvent
を使う
JavaScript
1SampleClassA.prototype.handleEvent = function handleEvent (event) { 2 this.updateResizeData(); 3 this.doDisplay(); 4 return true; 5}; 6 7var sampleA1 = new SampleClassA(); 8sampleA1.initialEvent(); 9addEventListener('resize', sampleA1, false);
(方法2) Function.prototype.bind
を使う
一つは onresize
のイベント定義時に bind
する方法。
JavaScript
1jQuery(window).on('resize', sampleA1.resizeEvent.bind(sampleA1));
もう一つは initialEvent()
時に予め bind
しておく方法(Lhankor_Mhy さんのアイデア)。
jsfiddleにサンプルをUPしています。
JavaScript
1SampleClassA.prototype.initialEvent = function initialEvent () { 2 this.resizeEvent = this.resizeEvent.bind(this); // 予め bind しておく 3 this.setDefaultData(); 4 this.doDisplay(); 5} 6 7var sampleA1 = new SampleClassA(); 8sampleA1.initialEvent(); 9jQuery(window).on('resize', sampleA1.resizeEvent); // bind 済みの resizeEvent を指定する 10 11/** 12 * ただし、sampleA1.resizeEvent は bind 済みの為、後から Function.prototype.call で this 値を変更できない(意図的に汎用的ではない) 13 * this 値を変更するためには SampleClassA.prototype.resizeEvent を経由する必要がある 14 */ 15console.log(sampleA1.resizeEvent !== SampleClassA.prototype.resizeEvent); // true 16sampleA1.resizeEvent.call(null, {target:{innerWidth: 777, innerHeight: 777}}); // Function.prototype.bind で束縛された this 値は書き換え不可能な為、TypeError にならない 17SampleClassA.prototype.resizeEvent.call({updateResizeData: Function(), doDisplay: console.log.bind(console)}, {target:{innerWidth: 777, innerHeight: 777}}); // bind されていない為、this 値を変更できる
(方法3) event.data
(jQuery API) を使う
JavaScript
1SampleClassA.prototype.resizeEvent = function resizeEvent (event) { 2 var thisArg = event.data.thisArg; 3 thisArg.updateResizeData(); 4 thisArg.doDisplay(); 5 return true; 6} 7 8var sampleA1 = new SampleClassA(); 9sampleA1.initialEvent(); 10jQuery(window).on('resize', {thisArg: sampleA1}, sampleA1.resizeEvent);
結論
jQuery に拘りがないのなら addEventListener
の handleEvent
を使う方法が最もスマートだと思います。
更新履歴
(2016/1/19 23:28追記)
Lhankor_Mhy さんのアイデアをお借りして initialEvent()
時に予め bind
しておくコードを追加しました。
Re: ゲストユーザーさん
投稿2016/01/16 11:42
編集2016/01/19 14:53総合スコア18194
0
私だったらですので、あまり参考にならないかも知れません。
###方法1 CoffeeScriptで書く
altJSの一つであるCoffeeScriptならSampleClassAをこんな風に書けます。
CoffeeScript
1class SampleClassA 2 constructor: -> 3 @data = null 4 @object = null 5 setDefaultData: -> 6 @data = 7 pointA: 10 8 pointB: 10 9 pointC: 10 10 pointD: 10 11 true 12 # 中略 13 resizeEvent: => 14 @updateResizeData() 15 @doDisplay() 16 true
CoffeeScriptでは通常のfuncitonは->
ですが、それとは別にthisをコードがある場所のthisに強制的にbindしたfunctionにする=>
があります。これを使えばthis違いの問題はおきません。また、CoffeeScriptならclass構文でわかりやすくクラスを書くことが出来るというのも利点の一つです。=>
はECMAScript 2015にもありますが、class構文で上のように書く方法は無かったはずです。
###方法2 react.jsやangular.jsを使う
クラスが必要と言うことはそれなりに複雑なイベントと描画を繰り返す処理になっていると思います。素のJavaScriptで頑張ろうとしても、やがて破綻するのが目に見えています(少なくとも、私だったら破綻します)。それなら、最初からフレームワークに頼って、その流儀にあったイベント管理方法を取り入れた方が早いと思っています。
投稿2016/01/16 09:14
総合スコア21751
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
SampleClassAに直接、pointADのデータの保有や値の操作を担当させるのではなく、pointADに関する処理は別のモデルクラスに行わせるのは、いかがでしょうか?
SampleLibraryというのが描画を担当しているクラスなのですよね?
であれば、SampleClassはMVCのコントローラーになっているのだと思うので、
モデルクラスを設けて、SampleClassにビューとモデルの橋渡しをさせるのでいかがでしょう?
投稿2016/01/18 13:29

退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
こういうイメージでしょうか。
javascript
1//クラス設定/////////// 2function SampleClassA() { 3 this.instanceOwnProperty; 4 this._init(); 5} 6function SampleClassB() { 7 this.instanceOwnProperty; 8 this._init(); 9} 10function SampleClass(){} 11 12SampleClass.prototype = { 13 _init: function(){ 14 this.data; 15 this.object; 16 var _self = this; 17 this.resizeEvent = function(){return _self._resizeEvent(_self)}; 18 }, 19 20 // ... 21 22 _resizeEvent: function(_self) { 23 _self.updateResizeData(); 24 _self.doDisplay(); 25 return true; 26 }, 27} 28SampleClassA.prototype = new SampleClass(); 29SampleClassA.prototype.ClassAProperty; 30SampleClassB.prototype = new SampleClass(); 31SampleClassB.prototype.ClassBProperty; 32 33//インスタンス生成/////////// 34var sampleA1 = new SampleClassA(); 35var sampleA2 = new SampleClassA(); 36var sampleB1 = new SampleClassB();
_selfを使わずにbind
でいいような気もしますが、メモリ的にはどっちがお得なんでしょう?
投稿2016/01/18 02:36
編集2016/01/18 10:52総合スコア37421
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

0
内容と違ってたらごめんなさい。
私の場合は、実態が一つしか存在しない場合、クラスは使いません。
例えば、サンプルクラスでしたら、
Javascript
1var Sample = {}; 2 3Sample.ClassA = { 4 5 Listener : { 6 resize : function ( evt ) { 7 var self = Sample.ClassA; 8 9 self.updateResizeData(); 10 } 11 } 12 13jQuery( window ).bind( 'resize', Sample.ClassA.Listener.resize );
のようにします。クラスというより、構造体、関数の集合体というとらえ方です。
Sample.ClassA と毎回書かなければならないのはわずらわしいですが、this に縛られることもありません。
どうしても、実体が必要な場合でしたら、デリゲータを利用してもいいかもしれません。
Javascript
1function delegater ( obj, func ) { 2 return function () { func.apply( obj, arguments ); } 3} 4 5var sampleA1 = new SampleClassA(); 6//初期イベント 7sampleA1.initialEvent(); 8 9//windowサイズ変更時のイベント等 10jQuery( window ).on( 'resize', delegater( sampleA1, sampleA1.Listener.resize ) );
それか、包括してしまうか
Javascript
1var Sample = (function ( listener ){ 2 return function ( value ) { 3 this.value = value; 4 this.Listener = listener( this ); 5 }; 6})( function ( self ) { 7 return { 8 resize : function ( evt ) { 9 console.log( self.value ); 10 } 11 }; 12} ); 13 14var s1 = new Sample( 'XXX' ); 15var s2 = new Sample( 'YYY' ); 16 17jQuery( window ).bind( 'resize', s1.Listener.resize ); 18jQuery( window ).unbind( 'resize', s1.Listener.resize ); 19jQuery( window ).bind( 'resize', s2.Listener.resize );
投稿2016/01/17 09:26
編集2016/01/17 09:28総合スコア34
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
すでに coffeescript で書く例が投稿されていますが、
私も coffeescript で書いてみることを提案したいです。
以下のページで動作を試せます。
http://jsdo.it/katoy/GWhY
resize イベントがなぜか拾えなかったので、 clock イベントで doDispley() が呼ばれるようにしています。
coffeescript
1class SampleClass 2 @data: null 3 # @object: null 4 5 setDefaultData: -> 6 @data = 7 pointA: 10 8 pointB: 10 9 pointC: 10 10 pointD: 10 11 true 12 13 initialEvent: -> 14 @setDefaultData() 15 @doDisplay() 16 true 17 18 resizeEvent: -> 19 @updateResizeData() 20 @doDisplay() 21 true 22 23 doDisplay: -> 24 # @object = new (SampleLibrary.display)(@data) 25 alert("data=[" + @data.pointA + ", " + @data.pointB + ", " + @data.pointC + ", " + @data.pointD) 26 true 27 28 updateResizeData: -> 29 @data.pointA++ 30 @data.pointC++ 31 true 32 33$ -> 34 sample01 = new SampleClass() 35 sample01.initialEvent() 36 37 $("#str").click -> 38 sample01.resizeEvent() 39 false
coffeescript のコードは javascript に変換できます。
javascript に変換すると次のようになります。
javascript
1var SampleClass; 2 3SampleClass = (function() { 4 function SampleClass() {} 5 6 SampleClass.data = null; 7 8 SampleClass.prototype.setDefaultData = function() { 9 this.data = { 10 pointA: 10, 11 pointB: 10, 12 pointC: 10, 13 pointD: 10 14 }; 15 return true; 16 }; 17 18 SampleClass.prototype.initialEvent = function() { 19 this.setDefaultData(); 20 this.doDisplay(); 21 return true; 22 }; 23 24 SampleClass.prototype.resizeEvent = function() { 25 this.updateResizeData(); 26 this.doDisplay(); 27 return true; 28 }; 29 30 SampleClass.prototype.doDisplay = function() { 31 alert("data=[" + this.data.pointA + ", " + this.data.pointB + ", " + this.data.pointC + ", " + this.data.pointD); 32 return true; 33 }; 34 35 SampleClass.prototype.updateResizeData = function() { 36 this.data.pointA++; 37 this.data.pointC++; 38 return true; 39 }; 40 41 return SampleClass; 42 43})(); 44 45$(function() { 46 var sample01; 47 sample01 = new SampleClass(); 48 return sample01.initialEvent(); 49}); 50 51$(window).click(function() { 52 alert("click"); 53 return true; 54});
備考:
coffeesctpt コードの作成は、実は質問文にある javascript コードを js2coffee というツールを
つかって、coffeescript に変換した結果をベースにして編集をしました。
投稿2016/01/16 11:48
総合スコア22328
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。