JavaScriptパターン #3

リテラルコンストラクタ

オブジェクトの定義
// オブジェクトリテラル記法で定義
var dog = {
  name: 'Benji',
  getName: function() {
    return this.name;
  }
};

// 組み込みのコンストラクタで定義
var dog = new Object();
dog.name = 'Benji';
dog.getName = function() {
  return this.name;
};


リテラルで定義する利点

  • オブジェクトは単純で変更可能なハッシュなのでクラスから生成する必要が無い。
  • スコープの解決がいらない。コンストラクタを使うと、Object()が呼ばれた場所からグローバルなObjectコンストラクタを見つける必要がある。

→オブジェクトリテラルが使えるのであれば、new Object()を使う理由は特にない。

独自のコンストラクタ関数

var Person = function(name) {
  this.name = name;
  this.say = function() {
    return 'I am ' + name;
  };
};

var me = new Person('daigo3');
me.say();    // "I am daigo3"

new Person()の様に呼び出すと、関数内部で空のオブジェクトをが作成されてthisで参照できる様になる。コンストラクタは暗黙的にthisを返す(別のオブジェクトを返していない場合)。
※実際にはプロトタイプを継承しているので「空のオブジェクト」ではない


上の例ではthis.sayの様にメソッドを追加しているが、newする度に同じメソッドが生成されてしまうので、プロトタイプに追加する方が良い。→メソッドの様な再利用可能なメンバはプロトタイプで定義する。

Person.prototype.say = function() {
  return 'I am ' + this.name;
};
newを忘れた場合

コンストラクタ関数内のthisがグローバルオブジェクトを参照してしまう。

var Person = function(name) {
  this.name = name;
};

Person.prototype.say = function() {
  return 'I am' + this.name;
};

var p = Person('daigo3');

console.log(p.name);    // TypeError: p is undefined
console.log(window.name);
console.log(name);

ただしECMAScript5では、strictモードの場合はthisはグローバルオブジェクトを指さなくなる。

配列

リテラルを使う方が簡潔。

// リテラル
var arr1 = [1, 2, 3];
// 組み込みコンストラクタ
var arr2 = new Array(1, 2, 3);

console.log(arr1, arr2);    //[1, 2, 3] [1, 2, 3]
console.log(arr1.constructor === Array);    // true
console.log(arr2.constructor === Array);    // true

// コンストラクタに引数を1つだけ渡した場合は
// その長さを持つ空の配列になる
var arr = new Array(3);
console.log(arr.length);    // 3
console.log(arr[0]);        // undefined

// 浮動小数点を長さにしてしてみる
var arr = new Array(3.14);    // RangeError: invalid array length
配列かどうかのチェック

配列はオブジェクトなのでtypeof演算子を使うと"object"が返ってきてしまう。

var arr = [1, 2 ,3];
console.log(typeof arr === 'object');    // true
var arr = [1, 2, 3];

// instanceofを使う
// IEのバージョンによっては、フレームをまたがって使うと正しくチェックできない
console.log(arr instanceof Array);

// ECMAScript5のArray.isArray()を使う
// console.log(Array.isArray(arr));

// Object.prototype.toString()を使う
// isArrayが使えない場合
if(typeof Array.isArray === 'undefined') {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  }
}

console.log(Array.isArray(arr));

JSON

JSONとオブジェクトリテラルの違い

// JSONデータを評価する
var jstr = '{ "key": "value"}';
var data = JSON.parse(jstr);
console.log(data.key);     //"value"

// JSONデータにシリアライズする
var dog = {
  name: 'Maru',
  legs: 4
}

var jsonstr = JSON.stringify(dog);    //{"name":"Maru","legs":4}

正規表現リテラル

var str = 'abc123XYZ';

// リテラル
var no_letters = str.replace(/[a-z]/gi, '');
console.log(no_letters);    // 123

// コンストラクタ
var letters = str.replace(new RegExp('[0-9]', 'gi'), '');
console.log(letters);    //abcXYZ


リテラルの方が簡潔に書けるが、動的に正規表現を作成する場合にはコンストラクタを使った方が良い。

エラーオブジェクト

name,messageの様なプロパティの他にも、エラーが発生した行番号などを示すプロパティがあるがブラウザ拡張だったりするので、一貫性が無い。
throwでは任意のオブジェクトを指定できるのでカスタムのエラーオブジェクトを作る事ができる。

try {
  // ...
  throw {
    name: 'MyErrorType',
    message: 'oops',
    extra: 'extra message',
    remedy: errorHandler  //  エラーを処理するハンドラ
  }
} catch (e) {
  alert(e.message);  // oops
  e.remedy();           // エラーを処理する(errorHandler)
}