質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.32%

javascriptのプログラム構造について

受付中

回答 6

投稿

  • 評価
  • クリップ 7
  • VIEW 2,312
退会済みユーザー

退会済みユーザー

以下のjavascript関数について良いパターンを教えてください。

関数では、ユーザ操作等のイベントに応じて、インスタンス内のメソッドでdataを更新し、表示を切り替えようとしています。
data及び表示の管理をインスタンスのみにさせ、ユーザ操作等のイベントをインスタンスに対して伝える仕様にできれば綺麗かと考えているのですが、
実際には下記はthisの仕様によりエラーとなります。
(ユーザ操作等のイベントの場合イベント発生元がthisになるので)

単にエラーを回避して動かすことはできるのですが、
出来るだけ良いプログラム構造としたいです。

※クラス・インスタンスは複数種類存在します(SampleClassA、SampleClassB…、sampleA1、sampleA2、sampleB1...)

//呼び出し側の記載///////////
var sampleA1 = new SampleClassA();
//初期イベント
sampleA1.initialEvent();
//windowサイズ変更時のイベント等
$(window).on('resize', sampleA1.resizeEvent)


//SampleClassA///////////
function SampleClassA() {
    this.data;
    this.object;
}
SampleClass.prototype = {
    setDefaultData: function() {
        this.data = {
            pointA: 10,
            pointB: 10,
            pointC: 10,
            pointD: 10,
        };
        return true;
    },
    doDisplay: function() {
        this.object = new SampleLibrary.display(this.data);
        return true;
    },
    initialEvent: function() {
        this.setDefaultData;
        this.doDisplay;
        return true;
    },
    updateResizeData: function() {
        this.data.pointA = this.data.pointA++;
        this.data.pointC = this.data.pointC++;
        return true;
    },
    resizeEvent: function() {
        this.updateResizeData; //エラー!!!
        this.doDisplay;
        return true;
    },
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 6

+6

 前提

おそらく、ご掲示のコードはテストしてないのではないでしょうか。
sampleA1.initialEvent() の行で TypeError になります。

sampleA1.initialEvent();  // TypeError: sampleA1.initialEvent is not a function

また、様々な部分で関数呼び出しされていないため、正しく実行されません。
以上のコードを修正したとして下記コードになると思われます。
(地味に手間なのでサンプルでも動く事を確認してから掲載するようにしていただけると助かります)

'use strict';
function SampleClassA () { ; }

SampleClassA.prototype = { // (修正) SampleClassAに修正
  setDefaultData: function setDefaultData () {
    this.data = {
      pointA: 10,
      pointB: 10,
      pointC: 10,
      pointD: 10
    };
  },
  doDisplay: function doDisplay (/* [window] */) {
//  this.object = new SampleLibrary.display(this.data); // (修正) ReferenceError の為、コメントアウト

    if (arguments.length > 0) {
      var window = arguments[0];
      console.log(window.innerWidth, window.innerHeight);
    }
    console.log(this.data);
  },
  initialEvent: function initialEvent () {
    this.setDefaultData(); // (修正) 関数呼び出しする
    this.doDisplay(); // (修正) 関数呼び出しする
  },
  updateResizeData: function updateResizeData () {
    var data = this.data;
    ++data.pointA;  // (修正) インクリメントされていなかった不具合を修正
    ++data.pointC;  // (修正) インクリメントされていなかった不具合を修正
  },
  resizeEvent: function resizeEvent (event) {
    console.log(event);
    this.updateResizeData(); // TypeError: this.updateResizeData is not a function  (修正) 関数呼び出しする
    this.doDisplay(event.target); // (修正) 関数呼び出しする
  }
};

SampleClassA.prototype.initialEvent = function initialEvent () {
  this.resizeEvent = this.resizeEvent.bind(this);
  this.setDefaultData(); // (修正) 関数呼び出しする
  this.doDisplay(); // (修正) 関数呼び出しする
}

// (修正) 呼び出しコードを後ろに持ってくる
var sampleA1 = new SampleClassA();
//初期イベント
sampleA1.initialEvent();
//windowサイズ変更時のイベント等
jQuery(window).on('resize', sampleA1.resizeEvent);

 解決法

 (方法1) addEventListener の handleEvent を使う

SampleClassA.prototype.handleEvent = function handleEvent (event) {
  this.updateResizeData();
  this.doDisplay();
  return true;
};

var sampleA1 = new SampleClassA();
sampleA1.initialEvent();
addEventListener('resize', sampleA1, false);

 (方法2) Function.prototype.bind を使う

一つは onresize のイベント定義時に bind する方法。

jQuery(window).on('resize', sampleA1.resizeEvent.bind(sampleA1));

もう一つは initialEvent() 時に予め bind しておく方法(Lhankor_Mhy さんのアイデア)。
jsfiddleにサンプルをUPしています。

SampleClassA.prototype.initialEvent = function initialEvent () {
  this.resizeEvent = this.resizeEvent.bind(this); // 予め bind しておく
  this.setDefaultData();
  this.doDisplay();
}

var sampleA1 = new SampleClassA();
sampleA1.initialEvent();
jQuery(window).on('resize', sampleA1.resizeEvent); // bind 済みの resizeEvent を指定する

/**
 * ただし、sampleA1.resizeEvent は bind 済みの為、後から Function.prototype.call で this 値を変更できない(意図的に汎用的ではない)
 * this 値を変更するためには SampleClassA.prototype.resizeEvent を経由する必要がある
 */
console.log(sampleA1.resizeEvent !== SampleClassA.prototype.resizeEvent); // true
sampleA1.resizeEvent.call(null, {target:{innerWidth: 777, innerHeight: 777}}); // Function.prototype.bind で束縛された this 値は書き換え不可能な為、TypeError にならない
SampleClassA.prototype.resizeEvent.call({updateResizeData: Function(), doDisplay: console.log.bind(console)}, {target:{innerWidth: 777, innerHeight: 777}});  // bind されていない為、this 値を変更できる

 (方法3) event.data (jQuery API) を使う

SampleClassA.prototype.resizeEvent = function resizeEvent (event) {
  var thisArg = event.data.thisArg;
  thisArg.updateResizeData();
  thisArg.doDisplay();
  return true;
}

var sampleA1 = new SampleClassA();
sampleA1.initialEvent();
jQuery(window).on('resize', {thisArg: sampleA1}, sampleA1.resizeEvent);

 結論

jQuery に拘りがないのなら addEventListener の handleEvent を使う方法が最もスマートだと思います。

 更新履歴

(2016/1/19 23:28追記)
Lhankor_Mhy さんのアイデアをお借りして initialEvent() 時に予め bind しておくコードを追加しました。

Re: ゲストユーザーさん

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+3

私だったらですので、あまり参考にならないかも知れません。

方法1 CoffeeScriptで書く

altJSの一つであるCoffeeScriptならSampleClassAをこんな風に書けます。

class SampleClassA
  constructor: ->
    @data = null
    @object = null
  setDefaultData: ->
    @data = 
      pointA: 10
      pointB: 10
      pointC: 10
      pointD: 10
    true
  # 中略
  resizeEvent: =>
    @updateResizeData()
    @doDisplay()
    true


CoffeeScriptでは通常のfuncitonは->ですが、それとは別にthisをコードがある場所のthisに強制的にbindしたfunctionにする=>があります。これを使えばthis違いの問題はおきません。また、CoffeeScriptならclass構文でわかりやすくクラスを書くことが出来るというのも利点の一つです。=>はECMAScript 2015にもありますが、class構文で上のように書く方法は無かったはずです。

方法2 react.jsやangular.jsを使う

クラスが必要と言うことはそれなりに複雑なイベントと描画を繰り返す処理になっていると思います。素のJavaScriptで頑張ろうとしても、やがて破綻するのが目に見えています(少なくとも、私だったら破綻します)。それなら、最初からフレームワークに頼って、その流儀にあったイベント管理方法を取り入れた方が早いと思っています。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

すでに coffeescript で書く例が投稿されていますが、
私も coffeescript で書いてみることを提案したいです。

以下のページで動作を試せます。
http://jsdo.it/katoy/GWhY

resize イベントがなぜか拾えなかったので、 clock イベントで doDispley() が呼ばれるようにしています。

class SampleClass
  @data: null
  # @object: null

  setDefaultData: ->
    @data =
      pointA: 10
      pointB: 10
      pointC: 10
      pointD: 10
    true

  initialEvent: ->
    @setDefaultData()
    @doDisplay()
    true

  resizeEvent: ->
    @updateResizeData()
    @doDisplay()
    true

  doDisplay: ->
    # @object = new (SampleLibrary.display)(@data)
    alert("data=[" + @data.pointA + ", " + @data.pointB + ", " + @data.pointC + ", " + @data.pointD)
    true

  updateResizeData: ->
    @data.pointA++
    @data.pointC++
    true

$ ->
  sample01 = new SampleClass()
  sample01.initialEvent()

  $("#str").click ->
    sample01.resizeEvent()
    false

coffeescript のコードは javascript に変換できます。
javascript に変換すると次のようになります。

var SampleClass;

SampleClass = (function() {
  function SampleClass() {}

  SampleClass.data = null;

  SampleClass.prototype.setDefaultData = function() {
    this.data = {
      pointA: 10,
      pointB: 10,
      pointC: 10,
      pointD: 10
    };
    return true;
  };

  SampleClass.prototype.initialEvent = function() {
    this.setDefaultData();
    this.doDisplay();
    return true;
  };

  SampleClass.prototype.resizeEvent = function() {
    this.updateResizeData();
    this.doDisplay();
    return true;
  };

  SampleClass.prototype.doDisplay = function() {
    alert("data=[" + this.data.pointA + ", " + this.data.pointB + ", " + this.data.pointC + ", " + this.data.pointD);
    return true;
  };

  SampleClass.prototype.updateResizeData = function() {
    this.data.pointA++;
    this.data.pointC++;
    return true;
  };

  return SampleClass;

})();

$(function() {
  var sample01;
  sample01 = new SampleClass();
  return sample01.initialEvent();
});

$(window).click(function() {
  alert("click");
  return true;
});


備考:
 coffeesctpt コードの作成は、実は質問文にある javascript コードを js2coffee というツールを
つかって、coffeescript に変換した結果をベースにして編集をしました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

内容と違ってたらごめんなさい。
私の場合は、実態が一つしか存在しない場合、クラスは使いません。

例えば、サンプルクラスでしたら、

var Sample = {};

Sample.ClassA = {

  Listener : {
    resize : function ( evt ) {
      var self = Sample.ClassA;

      self.updateResizeData();
    }
  }

jQuery( window ).bind( 'resize', Sample.ClassA.Listener.resize );

のようにします。クラスというより、構造体、関数の集合体というとらえ方です。
Sample.ClassA と毎回書かなければならないのはわずらわしいですが、this に縛られることもありません。

どうしても、実体が必要な場合でしたら、デリゲータを利用してもいいかもしれません。

function delegater ( obj, func ) {
  return function () { func.apply( obj, arguments ); }
}

var sampleA1 = new SampleClassA();
//初期イベント
sampleA1.initialEvent();

//windowサイズ変更時のイベント等
jQuery( window ).on( 'resize', delegater( sampleA1, sampleA1.Listener.resize ) );

それか、包括してしまうか

var Sample = (function ( listener ){
    return  function ( value ) {
        this.value = value;
        this.Listener = listener( this );
    };
})( function ( self ) {
    return {
        resize : function ( evt ) {
            console.log( self.value );
        }
    };
} );

var s1 = new Sample( 'XXX' );
var s2 = new Sample( 'YYY' );

jQuery( window ).bind( 'resize', s1.Listener.resize );
jQuery( window ).unbind( 'resize', s1.Listener.resize );
jQuery( window ).bind( 'resize', s2.Listener.resize );

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

こういうイメージでしょうか。

//クラス設定///////////
function SampleClassA() {
    this.instanceOwnProperty;
    this._init();
}
function SampleClassB() {
    this.instanceOwnProperty;
    this._init();
}
function SampleClass(){}

SampleClass.prototype = {
    _init: function(){
      this.data;
      this.object;
      var _self = this;
      this.resizeEvent = function(){return _self._resizeEvent(_self)};      
    },

    // ...

    _resizeEvent: function(_self) {
        _self.updateResizeData();
        _self.doDisplay();
        return true;
    },
}
SampleClassA.prototype = new SampleClass();
SampleClassA.prototype.ClassAProperty;
SampleClassB.prototype = new SampleClass();
SampleClassB.prototype.ClassBProperty;

//インスタンス生成///////////
var sampleA1 = new SampleClassA();
var sampleA2 = new SampleClassA();
var sampleB1 = new SampleClassB();

_selfを使わずにbindでいいような気もしますが、メモリ的にはどっちがお得なんでしょう?

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/01/19 23:58

    _init() 時に変数束縛する発想は面白いと思います。
    _resizeEvent, resizeEvent が並んでいますが、prototype-chain を利用して上書きしたらすっきり書ける気がしました。
    Function#bind https://jsfiddle.net/066ko0qg/
    関数式 https://jsfiddle.net/066ko0qg/1/
    Function() https://jsfiddle.net/066ko0qg/2/
    どれがいいかと問われれば、「Function#bind > Function() > 関数式」だと個人的には思います(変数のスコープの狭さ的な意味で)。

    キャンセル

  • 2016/01/20 09:27

    なるほど、複雑に考えずに上書き(というか自前のプロパティを持つというか)してしまえばいいんですね。たしかに、そちらの方がコードの見通しがいいですね。
    とても勉強になります、ありがとうございます。

    キャンセル

0

SampleClassAに直接、pointA~Dのデータの保有や値の操作を担当させるのではなく、pointA~Dに関する処理は別のモデルクラスに行わせるのは、いかがでしょうか?

SampleLibraryというのが描画を担当しているクラスなのですよね?
であれば、SampleClassはMVCのコントローラーになっているのだと思うので、
モデルクラスを設けて、SampleClassにビューとモデルの橋渡しをさせるのでいかがでしょう?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.32%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る