Help us understand the problem. What is going on with this article?

JavaScriptのプロパティディスクリプタ 〜 JSおくのほそ道 #019

More than 5 years have passed since last update.

こんにちは、ほそ道です。

今回は前回のGetter/Setterと関連深いプロパティディスクリプタを取り上げます。
プロパティディスクリプタとはオブジェクトのプロパティのメタ属性でデータアクセスに関する取り決めを保持しています。
MDNのjavascript1.8.5のドキュメントにまとまっていますが、ECMA5ではディスクリプタを設定してプロパティを制御するメソッドが沢山追加されています。
これはECMA5以降の仕様となっているようです。

目次はこちら

Object.getOwnPropertyDescriptor

ディスクリプタを見ていくにあたって、とりあえずgetOwnPropertyDescriptorというメソッドを動かしてみます。このメソッドはオブジェクトとプロパティを指定して、そのディスクリプタを取得できます。

とあるオブジェクト
var man = (function() {
  var _age = 0;
  return {
    get age() {
      return _age;
    },
    set age(val) {
      _age = val;
    }
  }
}());

前回取り上げたようなGetter/Setterを持つオブジェクトです。
それではageプロパティのディスクリプタを取得してみましょう。

getOwnPropertyDescriptorその1
man.age = 100;
var descriptor = Object.getOwnPropertyDescriptor(man, 'age');
console.log(descriptor);
// 出力
// {get: [Function: age],
//  set: [Function: age],
//  enumerable: true,
//  configurable: true }

今度はフツーにオブジェクトリテラルで宣言したプロパティのディスクリプタを取得してみましょう。

getOwnPropertyDescriptorその2
var foo = {
  bar: 100
};

var descriptor = Object.getOwnPropertyDescriptor(foo, 'bar');
console.log(descriptor);

// 出力
// {value: 100,
//  writable: true,
//  enumerable: true,
//  configurable: true }

ディスクリプタの内容が先ほどと異なりますね。
この違いについては下記にまとめます。

アクセサディスクリプタとデータディスクリプタ

getやsetという属性を持つディスクリプタの事をアクセサディスクリプタといいます
Getter/Setterを設定されたプロパティはこちらのディスクリプタとなります。

valueやwritableという属性を持つディスクリプタの事をデータディスクリプタといいます。
オブジェクトリテラルにおいて{kei:value}の形式で記述されたプロパティはこちらのディスクリプタになります。

言葉だけ覚えておくと資料読みの際等に役にたつかもしれません。

ディスクリプタのメタ属性

下記のモノで全てです。名前もわかり易いので一度使えば覚えられるレベルです。
writable属性をfalseにすることで定数を作る事もできますね。

名前 規定値 内容
configurable false trueの時、ディスクリプタ変更や、オブジェクトからプロパティ削除が可能
enumerable false trueの時、そのプロパティはkeysやfor...in...によるプロパティ列挙に現れる
value undefined プロパティの値。データディスクリプタのみ。
writable false trueの時、値を変更可能。データディスクリプタのみ。
get undefined Getter関数。アクセサディスクリプタのみ。
set undefined Setter関数。アクセサディスクリプタのみ。

Object.defineProperty

この関数はプロパティとディスクリプタを設定できます。

データディスクリプタを持つプロパティを生成
'use strict';
var man = {};
Object.defineProperty(man, 'age', {
  value: 20,
  writable: false,
});

man.age = 30;  // TypeError: Cannot assign to read only property 'age' of #<Object>

writableをfalseにして上書きしようとすると通常は無視され、strictモードにおいてはTypeErrorとなります。
Getterのみを設定したときと同じですね。

今度はアクセサディスクリプタを持つプロパティを生成してみましょう。

アクセサディスクリプタを持つプロパティを生成
var man = {
  _age: 0
};
Object.defineProperty(man, 'age', {
  get: function() {
    return this._age;
  },
  set: function(val) {
    this._age = val;
  },
});

defineSetter, defineGetterの時同様カプセル化が出来ておらず_age変数に直接アクセス出来てしまうのが相変わらず気になっていますが。。

このほかにも一度に複数のプロパティを設定できるObject.definePropertiesというメソッドもあります。

Object.create

Object.createは引数を二つとって新しいオブジェクトを返します。
第一引数はオブジェクトのprotoとなるオブジェクト。
第二引数はプロパティを列挙したオブジェクトです。プロパティの値はディスクリプタとなります。

createメソッド
var obj = Object.create({
  name: 'foo'
}, {
  age: {
    value: 20,
    writable: true
  }
});
console.log(obj.name);  // foo
console.log(obj.age);   // 20

その他のディスクリプタ操作

その他にはディスクリプタ一括変更とその状態確認の処理があります。

名前 内容
preventExtensions オブジェクトの拡張を禁止
isExtensible オブジェクトの拡張が許可されているか
seal オブジェクトの全プロパティの削除を禁止
isSealed オブジェクトが封印(seal)されているか
freeze オブジェクトの全プロパティに対して変更/削除を禁止する
isFrozen オブジェクトが凍結(freeze)されているか

試しに一番強制力の強いfreezeメソッドを見てみましょう

var man = {
  name: 'hosomichi',
  age: 20
};
Object.freeze(man);
man.age = 30;
man.address = 'Tokyo';
console.log(man.age);     // 20
console.log(man.address); // undefined

変更と拡張は無視されました。
strictモードだと下記のようなエラーが発生します。

  • 変更時
    TypeError: Cannot assign to read only property 'age' of #<Object>

  • 拡張時
    TypeError: Can't add property address, object is not extensible

まとめ

正直、いちいちディスクリプタを設定してられないと思うので
全部のオブジェクトにこれをやる必要は無いと思います。
しかし奔放すぎるJavaScriptのプロパティに対して結構な制御が可能となり、
下記のような場合は使えるのではないでしょうか。

  • 不変オブジェクトを造りたい!などオブジェクトの状態を保証したい場合
  • 定数を作りたい場合
  • 一部のプロパティをfor...in...などから隠蔽したい場合(enumerableがfalseの場合、for...in...ループでは登場しなくなりますが、Object.getOwnPropertyNames(object)を使用する事でプロパティ名の配列が取得出来ます。

今回は以上です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away