ES5 プロパティの属性について

めもです。

## がいよう

オブジェクトの各プロパティには以下の属性がある。

* データプロパティのみ
 * `[[Value]]`
 * `[[Writable]]`
* アクセサープロパティのみ
 * `[[Get]]`
 * `[[Set]]`
* 全プロパティ共通
 * `[[Enumerable]]`
 * `[[Configurable]]`

### プロパティ属性の取得

`Object.getOwnPropertyDescriptor(object, propertyName)` を使う。戻り値の `value` は `[[Value]]` に、`writable` は `[[Writable]]` に、以下同様、対応している。

```js
Object.getOwnPropertyDescriptor(window, 'NaN');
// => {value: NaN, writable: false, enumerable: false, configurable: false}
```

### プロパティ属性の設定

主に`Object.defineProperty(object, propertyName, descriptor)`を使う。省略した属性の値は `false` や `undefined` になる。

```js
Object.defineProperty(document, 'myProperty', {
  writable: true,
  enumerable: true,
});
Object.getOwnPropertyDescriptor(document, 'myProperty');
// => {value: undefined, writable: true, enumerable: true, configurable: false}
```

## データプロパティの [[Value]] と [[Writable]]

データプロパティは `[[Value]]` と `[[Writable]]` のどちらかがある (undefinedでない) プロパティのこと。 `obj.face = 'ヮ'` など普通に代入するとこれになる。

### [[Value]]

プロパティの値。

```js
var obj = {};
obj.face = 'ω';
Object.getOwnPropertyDescriptor(obj, 'face');
// => {value: "ω", writable: true, enumerable: true, configurable: true}
```

### [[Writable]]

プロパティの値を変更できるかどうか。
`[[Writable]]` でないプロパティに代入すると、strict モードなら `TypeError` 例外が投げられ、そうでなければ無視される。

```js
var obj = {};
Object.defineProperty(obj, 'face', {
  value: 'ᴗ',
  writable: false,
});
obj.face = 'へ';
console.log(obj.face); // => ᴗ
(function() {
  "use strict";
  obj.face = 'へ'; // => TypeError!
})();
```

## アクセサープロパティの [[Get]] と [[Set]]

アクセサープロパティは `[[Get]]` と `[[Set]]` のどちらかがある (undefinedでない) プロパティのこと。 

### [[Get]]

引数を受け取らない関数。プロパティの値を取得する時に呼ばれる。

```js
var obj = {};
Object.defineProperty(obj, 'name', {
  get: function() { return '坂上陽三'; }
});
console.log(obj.name); // => 坂上陽三
```

### [[Set]]

引数を1個受け取る関数。プロパティに値を割り当てる時に呼ばれる。

```js
var obj = {};
Object.defineProperty(obj, 'name', {
  get: function() { return '坂上陽三'; }
});
console.log(obj.name); // => 坂上陽三
```

## [[Enumerable]]

for in や `Object.keys` で列挙されるかどうか。

```js
var obj = {};
Object.defineProperty(obj, 'visible', {
  enumerable: true,
});
Object.defineProperty(obj, 'hidden', {
  enumerable: false,
});
Object.keys(obj); // => ["visible"]
```

## [[Configurable]]

[[Configure]] === false だと

* プロパティを削除できない
* [[Value]] 以外の属性を変更できない

```js
var obj = {};
Object.defineProperty(obj, 'x', {
  configurable: false,
});
delete obj.x; // => false
Object.defineProperty(obj, 'x', {writable: true}); // => TypeError!
Object.defineProperty(obj, 'x', {enumerable: true}); // => TypeError!
Object.defineProperty(obj, 'x', {configurable: true}); // => TypeError!
Object.defineProperty(obj, 'x', {get: Date.now}); // => TypeError!
Object.defineProperty(obj, 'x', {set: alert.bind(window)}); // => TypeError!
Object.defineProperty(obj, 'x', {value: 'y'}); // => ok
Object.x = 'X'; // => ok
```

## ついでにオブジェクトの [[Extensible]]

オブジェクトの `[[Extensible]]` 属性が `false` だとプロパティを追加できない。
`Object.preventExtensions` と `Object.isExtensible` を使ってアクセスする。

```js
var obj = {};
Object.isExtensible(obj); // => true
obj.x = 'x';
Object.preventExtensions(obj);
Object.isExtensible(obj); // => false
obj.y = 'y';
obj.y; // => undefined
(function() {
  "use strict";
  obj.y = 'y'; // => TypeError!
})();
```

## 属性をまとめて false にするメソッド

* `Object.seal`
* `Object.freeze`

`Object.seal(あざらし)` は `あざらし` の`[[Extensible]]` を `false` にし、各プロパティの `[[Configurable]]` を `false` にする。

`Object.freeze(あざらし)` は `Object.seal` の処理に加えて、`あざらし`の各プロパティの`[[Writable]]` を `false` にする。

たとえば配列を seal すると固定長になり、freeze すると不変になる。