ゲッターとセッター(プロパティ)を定義するには?[JavaScript].NET TIPS

JavaScriptでプロパティを定義するには、通常の構文に加えて、get/set構文を使う方法と、definePropertyメソッドを使う方法がある。

» 2016年05月25日 05時00分 公開
[山本康彦BluewaterSoft/Microsoft MVP for Windows Development]
.NET TIPS
Insider.NET

 

「.NET TIPS」のインデックス

連載目次

 JavaScriptのオブジェクトに単純なプロパティを追加するのは簡単だ。だが、プロパティの読み書き時に何か処理をさせたいこともあるだろう。それには読み書き用の関数を追加するしかないのだろうか? 実はECMAScript 5から、ゲッター/セッター(getter/setter)を使ったプロパティ定義が可能になっている。本稿ではその書き方を解説する。なお、ECMAScript 5に対応していない古いブラウザ(バージョン9未満のInternet Explorerなど)では動作しないので、ご注意いただきたい。

単純なプロパティ

 getter/setterの話の前に、それを使わない単純なプロパティを持ったオブジェクトの作り方を説明しておこう。

 同じ型のオブジェクトを何回も使うことを考え、オブジェクトを生成して返す関数を作ることにしよう。その関数の中で、オブジェクト初期化子を使う方法と、オブジェクトを作ってからプロパティを追加する方法がある。また、コンストラクタ関数を使う方法もある。以上の3通りのコード例を次に示す。

// オブジェクト初期化子を使う
function Person1(firstName, lastName) {
  return {
    firstName: firstName, // 「:」の左はプロパティ名、右はその初期値
    lastName: lastName
  };
}
var person1 = Person1("Shinji", "Kawasaki");
alert(person1.lastName + ", " + person1.firstName);
// 出力⇒Kawasaki, Shinji

// オブジェクトを作ってからプロパティを追加する
function Person2(firstName, lastName){
  var obj = {};
  obj.firstName = firstName; 
  // ↑左辺の「firstName」は追加するプロパティ(この1文だけで追加される)
  obj.lastName = lastName;
  return obj;
}
var person2 = Person2("Yasuhiko", "Yamamoto");
alert(person2.lastName + ", " + person2.firstName);
// 出力⇒Yamamoto, Yasuhiko

// コンストラクタ関数
function Person3(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}
var person3 = new Person3("Masahiko", "Isshiki"); // 注:ここのnewは必須
alert(person3.lastName + ", " + person3.firstName);
// 出力⇒Isshiki, Masahiko

単純なプロパティを持つオブジェクトを生成するコードの例(JavaScript)
firstNameプロパティとlastNameプロパティを持つオブジェクトを生成する関数と、その使用例。
3つ目のコンストラクタ関数は、その上のPerson2関数の省略記法のようにも見える。ただし、オブジェクト生成時にnew演算子が必須になる点が異なる。
なお、Person1関数/Person2関数を呼び出している箇所にもnew演算子を付けても構わない。JavaやC#のnew演算子とは違って、JavaScriptのnew演算子では呼び出した関数がオブジェクトを返してもよいのだ。その場合は、関数から返されたオブジェクトがnew演算子の結果になる(new演算子は最初に空のオブジェクトを作ってから関数を呼び出すので、この場合にはそのオブジェクトは無駄になる)。

getterを定義するには?

 オブジェクト初期化子の中でget構文を使うか、生成したオブジェクトに対してdefinePropertyメソッドを使う。

 先のコード例に、firstNameプロパティとlastNameプロパティを連結して返す読み取り専用のfullNameプロパティを追加すると、次のコードのようになる。

// オブジェクト初期化子を使う
function Person1(firstName, lastName) {
  return {
    firstName: firstName,
    lastName: lastName,
    get fullName() {
      return this.firstName + " " + this.lastName;
    },
  };
}
var person1 = Person1("Shinji", "Kawasaki");
alert(person1.fullName);
// 出力⇒Shinji Kawasaki

// オブジェクトを作ってからプロパティを追加する
function Person2(firstName, lastName) {
  var obj = {};
  obj.firstName = firstName;
  obj.lastName = lastName;
  Object.defineProperty(
    obj, // プロパティを追加したいオブジェクト
    "fullName", // プロパティ名
    {
      get: function () {
        return this.firstName + " " + this.lastName;
      }
    });
  return obj;
}
var person2 = Person2("Yasuhiko", "Yamamoto");
alert(person2.fullName);
// 出力⇒Yamamoto Yasuhiko

// コンストラクタ関数
function Person3(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  Object.defineProperty(
    this,
    "fullName",
    {
      get: function () {
        return this.firstName + " " + this.lastName;
      }
    });
}
var person3 = new Person3("Masahiko", "Isshiki");
alert(person3.fullName);
// 出力⇒Masahiko Isshiki

getterを持つオブジェクトを生成するコードの例(JavaScript)
太字の部分がgetterを追加している部分である。
オブジェクト初期化子の中では、get構文を使って簡潔に記述できる。
それ以外の場合ではdefinePropertyメソッドを使うが、やや煩雑になる。

setterを定義するには?

 getterと同様で、オブジェクト初期化子の中でset構文を使うか、生成したオブジェクトに対してdefinePropertyメソッドを使う。

 ただし、setterの場合(プロパティの設定時に何らかの処理を挟み込みたい場合)は、プライベートなメンバ変数が欲しくなるだろう。例えば、先ほどのコード例のlastNameプロパティをgetter/setterを使った実装に変更して、lastNameプロパティに値がセットされるときに全て大文字に変換したいとする。変換結果をどこかに保持しておかねばならないが、オブジェクトに新しいプロパティを追加してそこに保持してしまうと、オブジェクトの外部からアクセスできてしまう。変換結果を外部から見えないところに保持して、カプセル化を実現するにはどうしたらよいだろうか?

 カプセル化を実現するには、オブジェクトを生成する関数のローカル変数に値を保持すればよい(次のコード)。そのローカル変数は、生成したオブジェクトに結び付けられ、オブジェクトを生成する関数から抜けた後でも生き続けるのである(詳しくはクロージャを参照)。

// オブジェクト初期化子を使う
function Person1(firstName, lastName) {
  var _lastName;  // lastNameを保持しておく変数
  var p = {
    firstName: firstName,

    // lastNameプロパティ(getter/setter)
    get lastName() {
      return _lastName;
    },
    set lastName(name) {
      // lastNameプロパティへの代入時、大文字に変換する
      _lastName = name.toUpperCase();
    },

    get fullName() { ……省略…… },
  };
  p.lastName = lastName;
  return p;
}
var person1 = Person1("Shinji", "Kawasaki");
var person2 = Person1("Yasuhiko", "Yamamoto");
alert(person1.fullName + " / " + person2.fullName);
// 出力⇒Shinji KAWASAKI / Yasuhiko YAMAMOTO
// ↑この結果から、Person1のオブジェクトごとに
// 変数_lastNameが割り当てられていると分かる

// オブジェクトを作ってからプロパティを追加する
// ……次のコンストラクタ関数と同様なので、省略……

// コンストラクタ関数
function Person2(firstName, lastName) {
  var _lastName; // lastNameを保持しておく変数

  this.firstName = firstName;

  Object.defineProperty(
    this,
    "lastName",
    {
      get: function () {
        return _lastName;
      },
      set: function (name) {
        // lastNameプロパティへの代入時、大文字に変換する
        _lastName = name.toUpperCase();
      },
    });
  this.lastName = lastName;

  Object.defineProperty(this, "fullName", ……省略…… );
}
var person3 = new Person2("Masahiko", "Isshiki");
var person4 = new Person2("Bill", "Gates");
alert(person3.fullName + " / " + person4.fullName);
// 出力⇒Masahiko ISSHIKI / Bill GATES

getter/setterを持つオブジェクトを生成するコードの例(JavaScript)
太字の部分が、前のコードから変更した部分である。
オブジェクトを生成する関数の中のローカル変数_lastNameは、関数が実行されるたびに生成される。そのため、生成されたオブジェクトがアクセスしている_lastName変数は、オブジェクトごとに独立したものとなるのだ。

応用:既存のオブジェクトにgetter/setterを追加する

 definePropertyメソッドを使えば、既存のオブジェクトにgetter/setterを追加できる。標準ビルトインオブジェクトも例外ではない。次のコードは、年月日の文字列を返すgetterをDateオブジェクトに追加する例だ。

Object.defineProperty(
  Date.prototype,
  "formattedDate",
  {
    get: function () {
      // 年月日を取得し、月日はそれぞれ2桁の文字列(先頭ゼロ埋め)にする
      var yyyy = this.getFullYear().toString();
      var mm = ('0' + (this.getMonth() + 1).toString()).slice(-2);
      var dd = ('0' + this.getDate().toString()).slice(-2);
      // yyyy/MM/ddという書式で現在時刻を返す
      return yyyy + "/" + mm + "/" + dd;
    }
  });

var d = new Date();
alert(d.formattedDate);
// 出力例⇒2016/05/25

Dateオブジェクトに年月日を返すformattedDateプロパティを追加する例(JavaScript)

まとめ

 getter/setterを定義するには、オブジェクト初期化子の中でget構文を使うか、生成したオブジェクトに対してdefinePropertyメソッドを使う。definePropertyメソッドは、既存のオブジェクトにもgetter/setterを追加できる。

「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。