Yabu.log

ITなどの雑記

JavaScriptパターン第5章ノート

JavaScriptパターン ―優れたアプリケーションのための作法

JavaScriptパターン ―優れたアプリケーションのための作法

5賞ではオブジェクトの生成がテーマです。

3章でオブジェクトリテラルの使用を推奨しましたが、 そこから発展的な内容になります。

ここからは、とりあえず動くものを作る、というステージから 複数のライブラリを混ぜた時に問題なく動く、くらいのレベルアップのために必要なテクニックが紹介されているように思いました。JavaScriptでしっかり設計できる人はここに書かれていることくらいは抑えているんでしょうか。

名前空間パターン

グローバルオブジェクトを極力減らす工夫です。JavaScriptには名前空間に関する構文は提供されていませんが、 ライブラリやアプリケーションごとに、一つのグローバルオブジェクトを作成し、それのメンバとして 変数や関数を定義することでグローバル空間の汚染を防ぐことができます

例えばJQueryなんかもJQueryと$しかグローバル空間を汚染していませんね。

var test = "TEST";//NG

var testObj = {};
testObj.test = "TEST" //better

jQueryの良いところは,名前空間を汚染しないところです。すべての機能をロードした状態で,jQueryと$の2つのオブジェクトしか存在しません。

http://gihyo.jp/dev/feature/01/jquery/0001

汎用の名前空間関数

var MYAPP =
{
  message:"働きたくない"
}
var MYAPP = MYAPP || {};
MYAPP.namespace = function (ns_string) {
  var parts = ns_string.split('.'),
  parent = MYAPP, i;
  // 先頭の冗長なグローバルを取り除く
  if (parts[0] === "MYAPP") {
    parts = parts.slice(1);
  }
  for (i = 0; i < parts.length; i += 1) {
    // プロパティが存在しなければ作成する
    if (typeof parent[parts[i]] === "undefined") {
      parent[parts[i]] = {};
    }
    parent = parent[parts[i]];
  }
  return parent;
};

// 戻り値をローカル変数に代入する
var module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2; // true

var message = MYAPP.namespace('message');
console.log(message);//働きたくない <- 破壊的変更が行われない

複数のライブラリを利用している時に名前の衝突とか起こると大変そう

ちなみに、JQueryの場合は別のライブラリが$という名前を利用していた場合は、上書き時にローカル変数の_$に退避しnoConflictという関数を利用することで、戻すことができるらしい。

依存関係の宣言

var myFunction = function () {
  // 依存するモジュール
  var event = YAHOO.util.Event,
  dom = YAHOO.util.Dom;
  // 残りの関数で
  // eventとdomを使う
};

依存するモジュールをスコープの先頭に明示しておくことで、わかりやすいだけでなく、プロパティの入れ子の名前解決は1度しか行われないので処理速度が高速化される。(毎度YAHOO.util.Eventで呼ぶとその度に目的のプロパティをたどるコストがかかる)

Privateなプロパティ

JavaScriptはアクセス修飾子を提供していませんが、プライベートなスコープのメンバを返す関数を作成することで、擬似的にJavaのPrivateのような仕組みを導入できます

function Gadget(){
  var name = 'ipod';
  this.getName = function(){
    return name;
  };
}
var gadget = new Gadget();
console.log(gadget.getName());//ipod
console.log(gadget.name)//undefined

このプライベートメンバにアクセスするメソッドは特権メソッドというそうです。 ただし、オブジェクトを特権メソッドで返した場合は、オブジェクトが参照型なため、クライアント側で変更される恐れがあります。

function Gadget(){
  var name = {value:'ipod'}
  this.getName = function(){
    return name;
  };
}
var gadget = new Gadget();
var test = gadget.getName();
console.log(test.value);//ipod
test.value = "iPhone"
console.log(gadget.getName().value);//iPhone

モジュールパターン

コード構成のための構造を提供するらしい。

今までに学んだテクニックを駆使します。

  • 名前空間
  • 即時関数
  • プライベートメンバと特権メンバ
  • 依存関係の宣言

  • 1.即時関数の内部にプライベートメソッド、プライベートプロパティを書きなぐる。

  • 2.即時関数の戻り値のオブジェクトをパブリックAPIとして提供する。
  • 3.即時関数の内部かつreturn文の外を依存関係やプライベートメンバ、初期化処理の記述に利用する。

Javascriptがうまく扱いきれてないアクセス修飾子の仕組みを代替するような工夫 だと思いました。

サンドボックスパターン

モジュールパターンでも解決しきれない名前の衝突を解決します。 具体的には同名のライブラリの複数のバージョンを名前を衝突しないで使うような工夫です。 ちょっと何が書いてあるか、どう動かすか、何がありがたいのかがいまいちピンときませんでした。

静的メンバ

Math.max(3,5)みたいな書き方のこと。インスタンス化せずに使える。Javaでいうクラスメソッド。 プライベートな静的メンバ、パブリックな静的メンバの双方が作れる。ただし、Javascriptはアクセス修飾子はおろか、静的メンバのための特別な構文はない。

そんなJavaScriptだが以下のように工夫して静的メンバを実現している

//関数オブジェクト
var Gadget = function(){};

//関数目オブジェクトのメンバとして定義すると、パブリックなクラスメソッドになる
Gadget.isShiny = function(){
  return "you bet";
};


//プロトタイプにメソッドを追加すると、インスタンスメソッドになる。
Gadget.prototype.setPrice = function(price){
  this.price = price;
};

Gadget.isShiny();

var iphone = new Gadget();
iphone.setPrice(500);

//関数オブジェクトが保持しているメンバはインスタンスからは不可視。
iphone.isShiny();//undefined

//ただしプロトタイプに参照を代入すると、インスタンスからもアクセス可能。
Gadget.prototype.isShiny = Gadget.isShiny;

//ちょっと無理やりすぎないかw
iphone.isShiny();

なぜ関数オブジェクト?オブジェクトリテラルでいいじゃん、と思ったが、オブジェクトリテラルだとコンストラクタとして呼べないため関数オブジェクトである必要がある。

プライベートな静的メンバ

インスタンスメンバはクロージャーで実現可能。 ではプライベートな静的メンバは?

クロージャー的なものをpropatyに定義することで実現できる。

var Gadget = (function(){
  //静的変数/プロパティ
  var counter = 0,NewGadget;
  //新しいコンストラクタ実装
  NewGadget = function(){
    counter +=1;

  };
  //特権メソッド
  NewGadget.prototype.getLastId  = function(){
    return counter;
  };
  //コンストラクタを上書き
  return NewGadget;

}());//即座に実行
var iphone = new Gadget();
iphone.getLastId() //1
var ipad = new Gadget();
ipad.getLastId()//2

連鎖パターン

javaで言う所のメソッドチェーン。

var obj = {
  value:1,
  increment:function(){
    this.value +=1;
    return this;
  },
  add:function(v){
    this.value += v;
    return this;
  },
  shout:function(){
    console.log(this.value);
  }

}

obj.increment().add(3).shout();//5

//メソッドを一つずつ呼び出す
obj.increment();
obj.add(3);
obj.shout();

デメリット

デバッグが難しくなる。 Claen Codeによると「電車の衝突」(train wreck)パターンとのこと。

所感

javascriptはスコープ*1、アクセス修飾子を搭載していない、という前提から堅牢なコーディングをするにはどうすれば?というナレッジが詰まった章でした。個人的にJavaScriptそのものは堅牢なものを作るには向いておらず、まるで沼地に家を立てるようなそんなテクニックに感じました*2。かなり高度なHackではあると思いますが、個人的にはやってて楽しくなかったし、今後もやりたくないなと思いました(ゆとりプログラマーかな?)

本当に堅牢なものを作りたいならtypeScriptなどのaltjsで作成したものをトランスパイルして使うんだろうなと妄想します。

*1:ES6で追加済み

*2:そして実際に立ててしまうのがすごい!