プロパティには2種類あります。

最初の種類は データプロパティ です。私たちはすでにそれがどうやって動作するのかを知っています。実際、これまで使ってきたすべてのプロパティはデータプロパティでした。

2つ目のプロパティの種類は新しいものです。それは アクセサプロパティ です。それらは基本的には値の取得やセットをする関数ですが、外部コードからは通常のプロパティのように見えます。

Getters と setters

アクセサプロパティは “getter” と “setter” メソッドで表現されます。オブジェクトリテラルでは、それらは getset で表されますj.:

let obj = {
  get propName() {
    // getter, obj.propName を取得するときにコードが実行されます
  },

  set propName(value) {
    // setter, obj.propName = value 時にコードが実行されます
  }
};

obj.propName が読まれたときに getter は動作し、setter は割り当てられたときです。

例えば、namesurname を持つ user オブジェクトを持っているとします。:

let user = {
  name: "John",
  surname: "Smith"
};

今、私たちは “John Smith” という値となる “fullName” プロパティを追加したいとします。もちろん、既存の情報のコピーペーストはしたくありません。ここで、アクセサとしてそれを実装することができます。:

let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

alert(user.fullName); // John Smith

外部からは、アクセサプロパティは通常の変数に見えます。それがアクセサプロパティの考え方です。私たちは、関数として user.fullName呼び出すのではなく、それを通常通り 読み込みます。: getter は背後で実行されます。

今のところ、 fullName は getter しか持っていません。 user.fullName = を指定しようとすると、エラーが発生します。

user.fullName の setter を追加してそれを修正しましょう。:

let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

// set fullName は指定された値で実行されます
user.fullName = "Alice Cooper";

alert(user.name); // Alice
alert(user.surname); // Cooper

今 “仮想” プロパティを持っています。 読み書き可能ですが、実際には存在しません。

アクセサプロパティは get/set でのみアクセス可能です

プロパティは、 “データプロパティ” か “アクセサプロパティ” のいずれかになりますが、両方にはなりません。

プロパティが get prop() または set prop() で定義されると、それはアクセサプロパティです。 なので、getter で読まなければなりません。それに値を割り当てたいならば、setter を使わなければなりません。

setter または getter だけがある場合もあります。この場合は、プロパティの読み込みまたは書き込みはできません。

アクセサディスクリプタ

アクセサプロパティのためのディスクリプタは、データプロパティと比べて異なります。

アクセサプロパティでは、valuewritable がありませんが、代わりに、getset 関数があります。

したがって、アクセサディスクリプタには次のものがあります:

  • get – 引数なしの関数で、プロパティが読まれたときに動作します。
  • set – 1つの引数をもつ巻数で、プロパティがセットされたときに呼ばれます。
  • enumerable – データプロパティと同じです。
  • configurable – データプロパティと同じです。

例えば、アクセサ fullNamedefineProperty で作るとき、getset をディスクリプタに渡すことができます。:

let user = {
  name: "John",
  surname: "Smith"
};

Object.defineProperty(user, 'fullName', {
  get() {
    return `${this.name} ${this.surname}`;
  },

  set(value) {
    [this.name, this.surname] = value.split(" ");
  }
});

alert(user.fullName); // John Smith

for(let key in user) alert(key);

プロパティはアクセサかデータプロパティのいずれかになれますが、両方に離れないことに再度注意してください。

もしも getvalue を同じディスクリプタで指定しようとすると、エラーになります。:

// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
  get() {
    return 1
  },

  value: 2
});

スマートな getters/setters

Getter/setter は、実際のプロパティ値をラッパーとして使用して、より多くのコントロールを得ることができます。

例えば、user で短すぎる名前を禁止したい場合、name を特別なプロパティ _name に格納することができます。そして、setter で割り当てをフィルタします。:

let user = {
  get name() {
    return this._name;
  },

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short, need at least 4 characters");
      return;
    }
    this._name = value;
  }
};

user.name = "Pete";
alert(user.name); // Pete

user.name = ""; // Name is too short...

技術的には、外部コードは user._name を使うことで、直接 name にアクセスできるかもしれません。しかし、アンダースコア "_" で始まるプロパティは内部のもので、外部のオブジェクトから触るべきではないということは広く知られています。

互換性のために使用する

getter と setter の裏にある素晴らしいアイデアの1つは – それらは “通常の” データプロパティを制御し、それをいつでも調整することができます。

例えば、データプロパティ nameage を使って user オブジェクトを実装し始めました。:

function User(name, age) {
  this.name = name;
  this.age = age;
}

let john = new User("John", 25);

alert( john.age ); // 25

…しかし、遅かれ早かれ、それを変更するかもしれません。より正確で便利のため、age の代わりに birthday を格納することに決めるかもしれません。:

function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;
}

let john = new User("John", new Date(1992, 6, 1));

さて、まだ age プロパティを使っている古いコードはどうすればよいでしょうか?

そのような箇所をすべて見つけて直していくこともできますが、時間がかかったり別の人が書いているコードであれば直すのが難しいかもしれません。その上、age は user が持っていても良いものですよね?場所によってはそれを必要とするかもしれません。

age の getter を追加すると、問題が緩和されます:

function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  // age は現在の日付と誕生日から計算されます
  Object.defineProperty(this, "age", {
    get() {
      let todayYear = new Date().getFullYear();
      return todayYear - this.birthday.getFullYear();
    }
  });
}

let john = new User("John", new Date(1992, 6, 1));

alert( john.birthday ); // birthday は利用可能です
alert( john.age );      // ...age も同様です

これで古いコードも機能します。

チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。