読者です 読者をやめる 読者になる 読者になる

yutaponのブログ

javascript界隈の興味あるネタを備忘録的に残しておく場所

Object.defineProperty()について調べた

TypeScript本の5章ではコンパイル後のJSファイルを読めるようにJavaScriptの仕様について解説されています。

Object.defineProperty()というメソッド、ご存知でしょうか。

このメソッドは主に書き換え不可能なオブジェクトプロパティを定義するため使います。

これ、今まで知りませんでした・・

ってことで、ここらへんをサイ本を見ながら復習してみます。

Object.defineProperty()とは

ECMAScript5からオブジェクトのプロパティに、値以外にある設定を付与することができるようになっています。

設定というのは、

  • 書き込み可能か
  • 列挙可能か
  • 再定義可能か

という情報になります。

Chromeのコンソールに以下のコードを貼り付けて実行すると、このような結果が得られます。

// これを貼り付けると
Object.getOwnPropertyDescriptor({x:1}, 'x')

// こういう結果が得られる
Object {value: 1, writable: true, enumerable: true, configurable: true}

valueが値で、それ以外がxに対する設定になります。

単に var a = {x:1};と定義した場合は、

  • writable: true (書き込み可)
  • enumerable: true (列挙可)
  • configurable: true (再定義可)

として定義されることになります。

writable

名前の通り、trueだと上書きを許可します。

falseだとプロパティに値を再代入しても無視されます。
(※ 'use strict'モードだと、TypeErrorがスローされる)

使いどころは値を変更されたくない場合ですね。

enumerable

trueだと列挙されます。

列挙というのは次のようなコードの場合です。

// こんなオブジェクトがあって
var o = {
    a : 1,
    b : 2,
    c : 3
};

// Object.keysでプロパティを列挙したり
var keys = Object.keys(o);
conosle.log(keys); // => ['a', 'b', 'c']

// for in で列挙するとき
var i;
for (i in o) {
    console.log(i); // => 'a', 'b', 'c'
}

enumerable: falseだと、列挙されなくなります。
(列挙されなくなるだけで、プロパティにアクセスして値を取得することはできます)

configurable

configurableはプロパティの設定を変更可能かどうかを指定するもので、
実はwritable: falseと設定しても、configurable: trueだと、値を書き換えることが可能です。

プロパティの変更を行うにはObject.defineProperty()を使います。

Object.defineProperty()の使い方

第一引数にオブジェクト、第二引数にオブジェクトのプロパティ名、第三引数に値と設定をまとめたオブジェクトを渡します。

たとえば、書き込み不可のプロパティをオブジェクトに追加するときはこんなかんじ。

// 書き込み不可のプロパティ
var obj = {};
Object.defineProperty(obj, 'x', {
    value        : 1,
    writable     : false,   // 書き込み不可
    enumerable   : true,
    configurable : true
});

console.log(obj.x); // => 1
obj.x = 99;
console.log(obj.x); // => 1

configure: trueなので、Object.defineProperty()を使い設定を変更することができます。

// 書き込み不可のプロパティ
var obj = {};
Object.defineProperty(obj, 'x', {
    value        : 1,
    writable     : false,   // 書き込み不可
    enumerable   : true,
    configurable : true
});

// 書き込み可能に変更
Object.defineProperty(obj, 'x', {
    writable : true
});

console.log(obj.x); // => 1
obj.x = 99;
console.log(obj.x); // => 99

また、configurable: falseのプロパティに対して、Object.defineProperty()を使い設定を変更しようとするとTypeError例外がスローされるそうです。

Object.defineProperties()

一度に複数のプロパティを作成したり、設定を変更したりしたい場合はObject.defineProperties()が使えます。

使い方

第一引数に対象となるオブジェクト、第二引数に作成(または変更)するプロパティ名と、値と設定からなるオブジェクトを渡します。

// プロパティを一度に作成・設定する
var obj = Object.defineProperties({}, {
    x : { value: 1, writable: true, enumerable: true, configurable: true },
    y : { value: 2, writable: true, enumerable: true, configurable: true }
});

拡張可属性(extensible)

拡張というのは、オブジェクトに新しくプロパティを追加することを指します。

たとえば、こんな書き方が拡張です。

var o1 = {};
o1.x = 1; // xプロパティを追加

拡張不可にすることができるようで、Object.preventExtensions()を使います。

var obj = {};
Object.preventExtensions(obj); // 拡張不可にする
obj.x = 10;
console.log(obj); // => {}

拡張ができないだけで、プロパティを削除することは可能です。

var obj = { x: 1, y: 2 };
Object.preventExtensions(obj);  // 拡張不可にする
delete obj.x;     // xを削除
console.log(obj); // => { y: 2 }

拡張を不可にして、オブジェクトのプロパティの削除も禁止するにはObject.seal()を使います。

var obj = { x: 1, y: 2 };
Object.seal(obj);  // 拡張不可、再定義不可にする
obj.x = 100;
obj.z = 99;
delete obj.y;
console.log(obj); // => { x: 100, y: 2 }

Object.seal()ではプロパティの削除は禁止できましたが、obj.xがデフォルトでwritable: trueなので再代入が可能になっています。

もっと厳しくオブジェクトの変更を禁止するにはObject.freeze()を使います。

var obj = { x: 1, y: 2 };
Object.freeze(obj);  // 拡張不可、再定義不可、書き込み不可にする
obj.x = 100;
obj.z = 99;
delete obj.y;
console.log(obj); // => { x: 1, y: 2 }

ちなみにObject.seal()Object.freeze()も、オブジェクトのプロパティ全てを再定義不可に設定するので、あとからObject.defineProperty()で変更することはできないので注意。

いろいろ紹介しましたが何が言いたかったかというと、
JavaScriptというのは言語仕様上、容易に値を変更したり、壊したりすることができるという認識だったのですが、ES5から追加されたこれらの仕込みを使うことで少しは強固なコードをかけそうです。

ちょっと設定に面倒臭さがあるので、オブジェクトの生成を抽象化して使うのが良さそう。

ライブラリを作る人は気にしたほうが良さそうです。

あと説明するときに、値と設定からなるオブジェクトとか言ってましたが、 ちゃんと名前があり、この記事で指していたのはデータディスクリプタのことです。

get, setを指定したりするアクセサディスクリプタというのもあるのですが、
今回は触れませんでした。

詳しい説明がこちらの記事でまとめられていたのでご紹介。
ES5, Property Descriptor解説 | 枕を欹てて聴く

おわりに

TypeScript本を読んでいたらサイ本読んでいた。

もうES6が出るところなのにES5について知らないことがたくさんありすぎた。

まだTypeScript本を全部読み終えてないので引き続き読んでいきます。