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

JavaScriptのGetter/Setter 〜 JSおくのほそ道 #018

More than 5 years have passed since last update.

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

今回はJavaScriptのGetterとSetterについてやって行きます。

目次はこちら

Getter

Getterの仕様

それでは早速、Getterの定義から。

Getterの定義
var man = {
  firstName: '',
  lastName: '',
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  },
};

getというキーワードを使用します。
下記の様な感じで使用できます。

Getterの使用1
man.firstName = 'Bill';
man.lastName = 'Gates';
console.log(man.fullName);  // Bill Gates

まあ予想通りの動きになります。
次にfullNameプロパティに直接アクセスしてみましょう

Getterの使用2
man.firstName = 'Bill';
man.lastName = 'Gates';
man.fullName = 'Steve Jobs';
console.log(man.fullName);  // Bill Gates

下から二行目のman.fullNameへの代入は華麗にスルーされました。
ちなみにuse strictしてstrictモード上で実行するとman.fullName = 'Steve Jobs';とアクセスした時にTypeErrorが発生して怒られます。
fullNameを直接セットする事は出来くなります。

TypeError
TypeError: Cannot set property fullName of #<Object> which has only a getter

Setter

Setterの仕様

続いてSetterもやっていきましょう。

Setterの定義
var man = {
  _age: 0,
  get age() {
    return this._age;
  },
  set age(val) {
    console.log('[debug]age=' + val);
    this._age = val;
  }
};

今度はsetのキーワードを使用します。
また、Setter宣言時には必ずしもGetterを宣言する必要は無いのですが(strictモードでも動く)、お作法的にはGetterも宣言するべきなようで、しておかないとJsLintからは注意されました。

Setterの注意点

Setterはなにがしか別名の変数に値を保存する必要があります。
例えば

動かないSetter
var man = {
  get age() {
    return this.age;
  },
  set age(val) {
    this.age = val;
  }
};

上記のようにすると代入時に再帰的にSetterがコールされて無限ループのようになりスタックサイズ超過が発生してしまいます。

Getter/Setterのメリット

この仕組みがどんなときに効力を発揮するのか考えてみました。

  • データのカプセル化をコンストラクタ宣言せずに、オブジェクトリテラルで行う事ができる

  • データゲット時にイベント的にログを吐く等追加処理が行える

カプセル化

下記のように関数でくるんでデータ保存先の変数を隠蔽すると、カプセル化できます。

クロージャでカプセル化
var man = (function() {
  var _age = 0;
  return {
    get age() {
      return _age;
    },
    set age(val) {
      _age = val;
    }
  }
}());

あと、下記のようにすると一見、うまいことSetter/Getter内でageという変数が使われているように見えなくもないですがグローバル空間にage変数が宣言されてしまっているので気を付けましょう。

グローバル汚染に気をつけろパターン
var man = {
  get age() {
    return age;
  },
  set age(val) {
    age = val;
  }
};
man.age = 10
console.log(age);  // 10

アンチパターン

Getter/Setterに関して、追加処理を行う事が出来ますが、この場合にやらない方が良さそうな事を挙げておきます。

×:Setterで引数の値を書き換える
-> まず利用者からは状況がつかめず想定外が発生する可能性が高まります。

×:Setterで値チェックを行い、不正な場合にthrow new Errorする
-> 結果的に代入文をtry〜catchすることになりかなり不自然になります。エラーを発生させるならsetAgeなどの関数にした方が良いでしょう。

オブジェクト定義後にGetter/Setterを追加する

最後に、ここまでset/getキーワードを使用したやり方を紹介してきましたが
このやり方はオブジェクト定義時にSetter/Getterを併せて定義する必要があります。
オブジェクト定義後にGetter/Setterを追加する事も出来るのでこちらも見ておきます。

オブジェクト定義後にGetter/Setterを追加
var man = {
  _age: 0
};

man.__defineGetter__('age', function() {
  return this._age;
});
man.__defineSetter__('age', function(val) {
  this._age = val;
});

__defineGetter____defineSetter__という関数を使います。
※非標準なメソッドでIEでは搭載されていません
ただし、このやり方だと内部でデータを持っている_ageをカプセル化出来ないように思います。
ココがずっと悩ましいなーと引っかかっているので上手く出来たら追記します。

今回は以上です。

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