Hachirog

都内で働いているWebエンジニアのブログです

Reveling Prototype Patternについて

前回はReveling Module Patternについて書きました。JavaScriptでもprivateとpublicの領域を明快に分けられるという利点がありましたが、複数のインスタンスを作る際に、privateな関数もpublicな関数ももろもろが複製されるので、あまり効率が良くなさそうというのが懸念でした。

そこで、その点を補ったのがReveling Prototype Pattern(リビーリングプロトタイプパターン)ということで、紹介記事を読んで試してみました。

Revealing Prototype Pattern - Techniques, Strategies and Patterns for Structuring JavaScript Code - Dan Wahlin

Revealing Prototype Patternによるオブジェクトの作り方

//最初にコンストラクタを定義
var MessageCard = function(decorationChar) {
  this.decoration = decorationChar;
  this.history = [];
}

//プロトタイプに機能を追加
MessageCard.prototype = function() {
  function create(to, message){
    var head = createHead(to);
    var body = createBody(message, this);
    var contents = head + body;
    this.history.push(contents);
    return contents;
  }

  function createHead(to){
    return 'Dear ' + to + '\n';
  }

  function createBody(message, thisObj){
    var line = createLine(message.length + 4, thisObj.decoration) + '\n';
    var body = line;
    body += thisObj.decoration + ' ' + message + ' ' + thisObj.decoration + '\n';
    body += line;
    return body;
  }

  function createLine(length, char){
    var line = '';
    for(var i = 0; i < length; i++){
      line += char;
    }
    return line;
  }

  function getHistory(index){
    return this.history[index];
  }

  return {
    create: create,
    getHistory: getHistory
  };

}();

var messageCard = new MessageCard('+');

console.log(messageCard.create('tanaka', 'hello'));
/*
Dear tanaka
+++++++++
+ hello +
+++++++++
*/

console.log(messageCard.getHistory(0));
/*
Dear tanaka
+++++++++
+ hello +
+++++++++
*/

最初にコンストラクタを用意して、そのプロトタイプを拡張するみたいですね。 少々厄介なところがthisの参照を引き回す箇所がある点です。

  function create(to, message){
    var head = createHead(to);
    var body = createBody(message, this); //thisを引き回す
    var contents = head + body;
    this.history.push(contents);
    return contents;
  }

  function createBody(message, thisObj){ //オブジェクトの参照を引数から受け取る
    var line = createLine(message.length + 4, thisObj.decoration) + '\n';
    var body = line;
    body += thisObj.decoration + ' ' + message + ' ' + thisObj.decoration + '\n';
    body += line;
    return body;
  }

これを次のように書くとうまくいきません。createメソッドからcreateBodyメソッドを呼んだときの this.decorationはundefinedになります。JavaScriptのthisの難しいところですね。

  function create(to, message){
    var head = createHead(to);
    var body = createBody(message);
    var contents = head + body;
    this.history.push(contents);
    return contents;
  }

  function createBody(message){
    //ここでのthisはこのオブジェクトを参照していない
    var line = createLine(message.length + 4, this.decoration) + '\n';
    var body = line;
    body += this.decoration + ' ' + message + ' ' + this.decoration + '\n';
    body += line;
    return body;
  }

まとめ

Revealing Prototype Patternはプロトタイプを使って実行上の効率が良くなるものの、thisの参照に気を払わなければならない箇所があり、気をつけないと実装ミスにつながりそうだなと感じました。(JavaScriptに慣れていればそんなことはないのかもしれませんね・・・。)

一方、Revealing Module Patternの方が直観に反せずコーディングできそうです。なので、オブジェクトを山のように作る事がなければRevealing Module Patternでもいいのではないでしょうか。例えばTodoアプリを作るとして、1画面に1000個のTodoを表示させることはないと思います。複数ユーザの処理をさばくサーバーサイドはともかく、クライアントサイドに関してはRevealing Prototype Patternにしようが、Revealing Module Patternにしようが、さほどパフォーマンス的に気になるレベルではないと思いますし、好みの方を選択すればいいのかなと思いました。