オブジェクトのプロパティ属性

JavaScript オブジェクトのプロパティ属性とは?

JavaScript オブジェクトのプロパティは、「書き込み禁止かどうか」とか「各種設定可能かどうか」といった、いくつかの属性を持っています。

これらをプロパティ属性 (property attribute) といいます。

例えば、次のコードをみてください。

let book = {
    title: "Math 1"
};

console.log(book.title); // Math 1

book.title = "Physics 1";

console.log(book.title); // Physics 1

このコードではbookオブジェクトのtitleというプロパティを書き換えていますね。

ごく普通のコードで特に目新しいところはないと思います。

でも実は、このように動作することにも、裏側には条件があるのです。

titleプロパティというのを作りましたが、このプロパティ属性には「書き込み可能」という属性があります。そしてその属性が「書き込みOK」となっているために、書き換えることができたのです。

「書き込み可能」という属性はwritableという名前の属性です。

オブジェクトリテラルでオブジェクトを作っている時には、writabletrueで作成されます。 このため、プロパティを書き換えることができたのです。もしfalseならば書き換えることはできません。

writableの他にもいくつかプロパティ属性があります。

さらにその他にも、オブジェクトのアクセサプロパティを構成するためのいわゆる「ゲッター」や「セッター」なども、プロパティ属性を使って設定することができます。

オブジェクト指向プログラミングで、プロパティを設定するためのメソッドをセッター (setter)、値を取得するためのメソッドをゲッター (getter) などと呼ぶことが多いです。

これらについてもう少し詳しくみていきましょう。

プロパティ属性の種類

上では「書き込み可能」かどうかを決めるwritableというプロパティ属性を例にとって、プロパティ属性がだいたいどんなものか説明しました。

もう少し詳しくみていきましょう。

プロパティ属性はプロパティディスクリプタで設定する

プロパティ属性はプロパティ・ディスクリプタ (property descriptor, プロパティ記述子) という種類のオブジェクトで設定します。

プロパティディスクリプタは次の名前と型のプロパティを持つオブジェクトです。

属性 データ型 デフォルト値
value Any undefined
writable Boolean false
enumerable Boolean false
configurable Boolean false
get Function オブジェクト または undefined undefined
set Function オブジェクト または undefined undefined
表. プロパティ・ディスクリプタの型

さて、プロパティは大きく分けて、「データ・プロパティ」と「アクセサ・プロパティ」に分類できます。

プロパティの分類は「JavaScript オブジェクトのプロパティ」で説明しています。

プロパティの種類によって、設定できるプロパティ属性は違います。

データプロパティのプロパティ属性

データプロパティのプロパティ属性には次の属性があります。

名前 値・主な動作の違いなど
value 数値、文字列、オブジェクト等
writable true value を変更可能できる
false value を変更できない
enumerable true for-in でプロパティが表示される
false for-in でプロパティが表示されない
configurable true 全ての設定変更可能
false 次の操作ができない
  • プロパティの削除
  • アクセサプロパティへの変更
  • enumerable の変更
get n/a
set n/a
表. データプロパティのプロパティ属性

value 属性は、プロパティから値を読み出した時に取得できる値です。

上の例で言うと、book.title = "Physics 1" とした時、 実はプロパティ属性のひとつであるvalueの値が "Physics 1"に書き換えられています。

writable 属性は value属性を書き換えられるかどうかをきめます。writabletrueならば書き換え可能で、falseならば書き換えできません。

value属性とwritable属性はデータプロパティに特有の設定です。アクセサプロパティとして設定できません。このため、 次のようにアクセサの設定 (get) と同時に設定するのは、プロパティディスクリプタが不正であるというエラーになります。

let book = Object.defineProperty({}, 'author', {
  value: 'Edgar Allan Poe',
  writable: false,
  enumerable: true,
  configurable: true,
  get() { // 同時に設定できない
    return 'Edgar Allan Poe';
  }
});

enumerable 属性はプロパティが列挙できるか決めるのに使います。falseにすると、for-in ループでみた時にプロパティのキーが見えてきません。

// enumerable, writable, configurable 全て true で作成される
let book = {
    title: "The Black Cat",
    author: "Edgar Allan Poe"
}; 

for (let p in book) {
    console.log(`${p}: ${book[p]}`);
}

// 'author' プロパティの enumerable 属性を false に変更
Object.defineProperty(book, 'author', {
    enumerable: false,
});

console.log('---');

for (let p in book) {
    console.log(`${p}: ${book[p]}`);
}

実行結果は次の通り。

configurablefalseにすると、 プロパティの削除、enumerableの変更、アクセサプロパティへの変更などができなくなります。(writable属性は変更できます)

'use strict';

let book = {
    title: "The Black Cat",
    author: "Edgar Allan Poe"
};

Object.defineProperty(book, 'title', {
    configurable: false,
});

delete book.title; // title プロパティの削除を試みる

for (let p in book) {
    console.log(`${p}: ${book[p]}`);
}

configurable属性がfalseであるために、プロパティの削除を試みた時にエラーが発生しています。

アクセサプロパティのプロパティ属性

アクセサプロパティのプロパティ属性には次の属性があります。

名前 値・主な動作の違いなど
value n/a
writable n/a
enumerable true for-in でプロパティが表示される
false for-in でプロパティが表示されない
configurable true 全ての設定変更可能
false 次の操作ができない
  • プロパティの削除
  • データプロパティへの変更
  • enumerable の変更
get 引数無しの
関数オブジェクト
読み取り時に返す値を設定
set 引数を一つ受け取る
関数オブジェクト
書き込み操作の時の動作を決める
表. アクセサプロパティのプロパティ属性

enumerable属性とconfigurable属性はデータプロパティのプロパティ属性のときと同様です。 列挙できるかどうか、属性を再設定できるかということに関わります。

get属性には、引数を取らない関数オブジェクトを設定します。このメソッドが返した値が、プロパティの値として読み取られます。

次の例では、オブジェクトを新規に作成し、Object.defineProperties()関数を利用して、 radiusというデータプロパティとareaというアクセサプロパティを作成します。

let circle = Object.defineProperties(
  {},
  {
    radius: {
      value: 1,
      writable: true,
      configurable: true,
      enumerable: true
    },
    area: {
      configurable: false,
      enumerable: true,
      get() {
        return Math.PI * Math.pow(this.radius, 2);
      }
    }
  }
);

for (let p in circle) {
  console.log(`${p}: ${circle[p]}`);
}

circle.radius = 2.5;

for (let p in circle) {
  console.log(`${p}: ${circle[p]}`);
}

この実行結果は次のようになります。

プロパティ属性の設定方法

プロパティ属性を細かく指定するための基本的な方法は、これまでに実例でみてきたように Object.defineProperty()、 または Object.defineProperties()を使って、プロパティディスクリプタを指定することです。

この他、より簡単な方法とか、注意点がいくつかありますので、ここでまとめて紹介します。

オブジェクトリテラルでアクセサを指定する方法

オブジェクトリテラルを使って書くと、「アクセサプロパティのプロパティ属性」の例であげたものと同等のオブジェクトが簡単に作れます。

ゲッターにするメソッドに getキーワードを付けることで、ゲッター (アクセサプロパティ) として作成されます。

let circle = {
    radius: 1,
    get area() {
        return Math.PI * Math.pow(this.radius, 2);
    }
}

circle.radius = 1.2;
console.log(circle.area); // 4.523893421169302

ゲッターになっているので、areaを読み取るのに()を付けていません。

オブジェクトリテラルを使って、セッター (setter) を作るには setキーワードを使って、プロパティを定義します。

次の例ではフライト番号 (エアラインを識別する二文字のアルファベットと4桁までの数字の組み合わせ) を受け取って、エアラインと数字に分解しています。

let n = {
  airline: '',
  number: '',
  get flightNumber() {
    console.log('** getter was called.');
    return `${this.airline}${this.number}`;
  },
  set flightNumber(val) {
    console.log('** setter was called.');
    if (val) {
      const m = val.match(/^([A-Z]{2})(\d{1,4})$/);
      if (m) {
        this.airline = m[1];
        this.number = m[2];
      }
    }
  }
};

n.flightNumber = 'NH117';
for (let p in n) {
  console.log(`${p}: ${n[p]}`);
}

この実行結果は次のようになります。

プロパティの作成方法の違いとプロパティ属性の違い

Object.defineProperty() 関数を使って、新しくオブジェクトを作成し、同時に一つデータプロパティを設定してみましょう。 このとき、次のようなプロパティディスクリプタを使うとします。

const desc = {
  value: 'The Black Cat'
};

ここではプロパティ属性をvalueしか指定していませんから、 他の属性はデフォルト値が使われます。つまり、上のプロパティディスクリプタは次のと同じ意味になります。

const desc = {
  value: 'The Black Cat',
  writable: false, //デフォルト値
  configurable: false, //デフォルト値
  enumerable: false, //デフォルト値
};

このため、次のコードではオブジェクトを新しく作成して、titleという名前のプロパティを 設定していますが、writable属性がfalseになります。

const desc = {
  value: 'The Black Cat'
};

let book = Object.defineProperty({}, 'title', desc);

console.log(book.title); // The Black Cat
book.title = 'The Whte Cat';
console.log(book.title); // The Black Cat

確かに代入操作を行ってもvalue値が変更できていません。

strictモードであれば、書き込みを試みた時にエラーが発生します。

このように Object.defineProperty() 関数もしくは Object.defineProperties()関数を使って、 明示的にプロパティディスクリプタを渡しているのに属性を指定しなかった場合には上表のデフォルト値が使われます。

一方、オブジェクトリテラルでプロパティを作成した場合はどうでしょうか。

let book = {
    title: "The Black Cat"
};

この場合のプロパティ属性は次の内容と同じになります。

let book = Object.defineProperty({}, 'title', {
    value: "The Black Cat",
    writable: true,
    configurable: true,
    enumerable: true,
});

オブジェクトリテラルを使って、そもそもプロパティディスクリプタを渡さなかった場合のデフォルト値は、Boolean は全て true になります。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 JavaScript 入門