Hatena::Groupjavascript

JavaScriptで遊ぶよ

 | 

2009-11-17

ECMAScript 5の"Object"

16:13

D3E に凝りずに、Mark Miller さんによる ECMAScript 5 紹介ビデオを見てた。

一番長い時間を裂いてたのはこれについて。

Object.create( prototype, {(name:attrs)*} )
Object.defineProperty( obj, name, attrs} )
Object.defineProperties( obj, {(name:attrs)*} )
Object.getOwnPropertyNames(obj) // => names
Object.getOwnPropertyDescripter(obj, name) // => attrs
Object.freeze( obj ) // => obj

普通はオブジェクトの内部属性までは触れないけれど、Object.create を使えば内部属性まで決めることができる。

ECMAScript 3 であった [[ReadOnly]], [[DontEnum]], [[DontDelete]] 内部属性がそれぞれ、[[Writable]], [[Enumerable]], [[Configurable]] となった。上記の attrs という部分には、writable, enumerable, configurable, get, set キーを持つハッシュを渡すこととなる。(当然ながら get, set はゲッター、セッター関数)

(以下では面倒なので [[Writable]] のことを単に writable と書いたりする)


Object.create

ES3 互換のオブジェクト定義。

var obj = { x : 1 };

これが以下と等価となる。

var obj = Object.create(Object.prototype, 
  { x : {
    value : 1,
    writable : true,     // x の値が変更できる
    enumerable : true,   // for (var k in obj) で x を辿ることができる
    configurable : true  // 下に説明
  }}
)

configurable は DontDelete を一般化したもので、これが false だと

  1. delete できない
  2. writable, enumerable, configurable の値を変更できない

ということになるらしい。


Object.defineProperty, defineProperties

一度作ったオブジェクトに特別な内部属性を持ったプロパティを定義したいときは

Object.defineProperties( obj , {
  toString: {
    value: function() {return 'ヒミツ'}, 
  }
});

または、

Object.defineProperty( obj , 'toString', {
  value: function() {return 'ヒミツ'}, 
});

などとする。

Object.create や defineProperty でプロパティを新しく定義する場合、writable, enumerable, configurable のデフォルトは false となるので、自分で書くときは true のものだけ書けばよい。(ビデオの30分目あたり)

ちなみに John Resig さんはこれらのデフォルトは true と書いてるけど、たぶん間違い。


Object.freeze

freeze の役割は3つ。

  1. そのオブジェクトのすべてのプロパティについて、writable 内部属性を false にする。
  2. そのオブジェクトのすべてのプロパティについて、configurable 内部属性を false にする。
  3. そのオブジェクト自体の extensible プロパティfalse にする。

writable が false だと、non-strict モード (ES3 互換モード) では単にその値が変わらないでスクリプトは実行し続け、strict モードだとエラーを出すようになるらしい。

3番目はつまり、そのオブジェクトに新しいプロパティを追加することができないということ。

ビデオにはちらっと言及されただけだけど、John Resig さんのブログによると、

Object.preventExtensions( obj )
Object.isExtensible( obj )

というのもあるらしい。


コンストラクタ内でオブジェクトをfreezeしたいとき

27分目あたり。

function Point(x, y) {
  return Object.freeze({
    x: +x,
    y: +y
  });
}

こんなふうにコンストラクタを作ったとすると、new Point(1, 2) instanceof Point === false となってしまうので、

function Point(x, y) {
  return Object.freeze( 
    Object.create( Point.prototype, {
      x : { value : +x, enumerable : true },
      y : { value : +y, enumerable : true}
    })
  );
}

とやると、ちゃんと new Point(1, 2) instanceof Point === true になってくれるので良い。(ほとんどの場合は上のでも問題ないと言ってるけど)

毎回こんなふうに書くのは面倒なので、Function.prototype.build というメソッドを作っちゃうのがいいらしい。

Object.defineProperty(
  Function.prototype,
  'build', 
  {
    value : function( attrMap ) {
      return Object.freeze( 
        Object.create( this.prototype, attrMap )
      );
    },
    writable : false,
    enumerable : false,
    configurable : false
  }
);

こうすることによって、

function Point(x, y) {
  return Point.build({
    x: {value : +x, enumerable: true},
    y: {value : +y, enumerable: true}
  });
}

new Point(1, 2) instanceof Point === true;
Point(1, 2) instanceof Point === true;

ちょっとは簡単に書ける。


疲れたので一旦おしまい

他の部分でおもしろかったところもあるけど、これに比べたらキモではないと思うのでまた別の機会にでも。


せっかくなので

John Resig さんが書いてるのもまとめとく。

Object.preventExtensions( obj )
Object.isExtensible( obj )

Object.seal( obj )
Object.isSealed( obj )

Object.freeze( obj )
Object.isFrozen( obj )

上の Object.freeze のところで書いた、Object.freeze の3つの役割

  1. そのオブジェクトのすべてのプロパティについて、writable 内部属性を false にする。(プロパティを書き換えられない)
  2. そのオブジェクトのすべてのプロパティについて、configurable 内部属性を false にする。(プロパティを消したり属性を変えたりできない)
  3. そのオブジェクト自体の extensible プロパティfalse にする。(プロパティを追加することができない)

このうち、seal は上の2つ、preventExtensions は最後のと同じことになる。どれについても、一度やったら戻すことはできないみたい。


Object.keys( obj )

オブジェクトについての enumerable なキーだけを配列として返す。ECMAScript 3 互換の for ( var prop in obj ) if ( obj.hasOwnProperty( prop ) ) と同じこと。

 |