LoginSignup
8
8

More than 5 years have passed since last update.

[JavaScript] 5. private プロパティ

Last updated at Posted at 2015-12-08

  1. クラスの定義
  2. クラスの継承
  3. 即時関数 と jQuery.readyイベント関数
  4. 即時関数でクラス定義
  5. private プロパティ
  6. Object.defineProperty/ies でプロパティ/メソッド定義

今回は private なプロパティ、すなわちクラスの外からアクセスできないプロパティの定義の方法を解説します。

カプセル化

プロパティをクラスの外からアクセスできなくすることを カプセル化 と言います。
不用意に値を書き換えたり、参照されることが無いようにするのが目的です。

アクセサ

カプセル化されたプロパティに外部からアクセスするには、アクセサ という関数を用います。プロパティの値を変更する(セットする)関数を セッター、値を取得する関数を ゲッター と言います。

private プロパティの定義

JavaScript疑似クラスでは、コンストラクタ関数のローカル変数が private プロパティとなります。ローカル変数であるゆえ、コンストラクタの外からはアクセスできなくなるわけです。

privateプロパティの定義
クラス名 = function(仮引数, ...) {
  var プライベートプロパティ名;
  var プライベートプロパティ名 = 初期値;
}

ただし、このままでは本当にコンストラクタ内だけでしか利用できないただのローカル変数です。

Object.defineProperty() / Object.defineProperties()

Object.defineProperty / Object.defineProperties は、オブジェクト(ここでは関数オブジェクト)に様々なプロパティをセットするための関数です。この関数を用いることで アクセサ(セッター、ゲッター) を定義することができます。

Object.defineProperty
Object.defineProperty(オブジェクト, プロパティ名, {パラメータ, ...});

1つ目の引数は、プロパティをセットする対象のオブジェクトです。
2つ目は、セットするプロパティの名前の指定です(文字列)。
3つ目には、セットするプロパティの内容をハッシュオブジェクトで定義します。

Object.defineProperties では複数のプロパティを一括で扱うことができます。

Object.defineProperty
Object.defineProperty(オブジェクト, {プロパティ名: {パラメータ, ...}, .... });

こちらでは2つ目の引数がハッシュオブジェクトになります。プロパティ名: {パラメータ, ...} のセットを複数並べて定義します。

Object.defineProperty(ies)のプロパティ・パラメータ

  • value
    • プロパティにセットする値(オブジェクト)を指定するキーです
  • set
    • セッター関数を指定するキーです
  • get
    • ゲッター関数を指定するキーです

セッター、ゲッターの定義

privateプロパティに対するセッター、ゲッターを定義するには次のように記述します。

セッター、ゲッターの定義
Object.defineProperty(コンストラクタ関数オブジェクト, プロパティ名, {
    set: function(仮引数) {
        プライベートプロパティ = 仮引数;
    },
    get: function() {
        return プライベートプロパティ;
    }
});

まず、1つ目の引数にコンストラクタ関数のオブジェクトを渡します。
2つ目には、外部からアクセスする際のプロパティ名を文字列で指定します。
3つ目のハッシュオブジェクトでは、 setget 2つのキーに対し、それぞれ セッター関数ゲッター関数をセットします。
セッター関数では引数で受け取った値をプライベートプロパティに代入します。
ゲッター関数ではプライベートプロパティの値を返すようにします。

以下はプライベートプロパティとそのセッター、ゲッターを持つクラスの例です。

MyClass-private.js
MyClass = function() {
    var _pri = null;

    Object.defineProperty(this, "pri", {
        set: function(value) {
            _pri = value;
        },
        get: function() {
            return _pri;
        }
    });
}

var obj = new MyClass();
obj.pri = "hoge";
document.write(obj.pri);

最後の2行で、プロパティ pri へ文字列 hoge を代入し、その値を出力しています。
しかし、プロパティ pri には実体はありません。実際には代入された値は セッター を介してプライベートプロパティ(ローカル変数)_pri に代入されています。
また ゲッター を介して _pri から値を取得して出力されます。
つまり、pri はあくまで _pri との中継役であり、実体は _pri にあるわけです。

アクセサ定義はコンストラクタの外に

しかし、この記述方法は望ましくないことがわかってきています。
関数オブジェクト内で関数の定義を行う、この場合ではコンストラクタ関数内でセッター、ゲッター(アクセサ)の関数定義を行うと、メモリコストの肥大化やメモリリークなどのリスクが増大します。
そこで、アクセサ関数はコンストラクタ関数の外で定義するようにします。

コンストラクタの外でアクセサ定義
クラス名 = function(仮引数, ...) {
    var プライベートプロパティ名;
    Object.defineProperty(this, プロパティ名, コールバック関数(プライベートプロパティ);
}

var コールバック関数名 = function(プライベートプロパティ) {
    return {
        set: function(仮引数) {
            プライベートプロパティ = 仮引数;
        },
        get: function() {
            return プライベートプロパティ;
        }
    };
}

Object.definePropertyの第3引数には、本来渡すべきハッシュオブジェクトを返す関数(コールバック関数)を指定します。その際、プライベートプロパティの変数(ローカル変数)を引数に渡します。
そして、コンストラクタ関数の外に、Object.definePropertyのプロパティパラメータを返す関数を定義します。この場合はセッター、ゲッター関数定義のハッシュオブジェクトを返すようにします。

なお、コールバック関数がグローバル関数にならないように、全体を即時関数の中に入れるのが望ましいでしょう。

前述のサンプルコードを変更したものが次のサンプルです。

MyClass-private.js
MyClass = (function() {
    var myClass = function() {
        var _pri = null;
        Object.defineProperty(this, "pri", accessPri(_pri));
    }

    var accessPri = function(_pri) {
        return {
            set: function(value) {
                _pri = value;
            },
            get: function() {
                return _pri;
            }
        };
    }

    return myClass;
});

メソッド内からのアクセス

このようにしてプライベートプロパティを実現することができますが、この手法でのプライベートプロパティはあくまで コンストラクタ関数内のローカル変数 です。したがって、コンストラクタ関数の外からは直接アクセスすることはできません。

一方、1.クラスの定義で解説した(Google推奨の) prototype を利用したメソッドの定義では、メソッドをコンストラクタ関数の外で定義しています。
つまりこの手法ですと、メソッド内から直接アクセスすることができないのです。

そこで、メソッドも Object.defineProperty/ies を使って定義します。
prototype を使用しないため少々手順が煩雑になりますが、この手法であれば private なプロパティにメソッド内からも直接アクセスできるようになります。

※ただし、この手法はあまりオススメしません。
アクセサを用いれば prototype 手法でもアクセスできますので、
どうしても メソッド内から privateプロパティに直接アクセスする必要がある場合に限りましょう。

コンストラクタの外でアクセサ定義
クラス名 = function(仮引数, ...) {
    var プライベートプロパティ名;
    Object.defineProperty(this, メソッド名, コールバック関数(プライベートプロパティ);
}

var コールバック関数名 = function(プライベートプロパティ) {
    return {
        value: function(仮引数, 仮引数) {
            〜 メソッドの処理 〜
            〜 ※プライベートプロパティへのアクセスが可能 〜
        }
    };
}

今回は Object.defineProperty/ies の value パラメータを使います。 value パラメータはプロパティにセットする値やオブジェクトを指定するキーです。
ここではメソッドとなる 関数オブジェクト をセットしています。

全体のサンプルコード

MyClass-private.html
<!DOCTYPE html>
<script>
/**
 * クラス MyClass
 */
MyClass = (function() {
    /**
     * コンストラクタ
     */
    var myClass = function(prop1, prop2) {
        // private プロパティを定義
        var _pri = null;

        // プロパティ、メソッドの定義
        Object.defineProperties(this, {
            prop1:   {value: prop1},
            prop2:   {value: prop2},
            pri:     accessPri(_pri),
            methodA: methodA(_pri),
        });
    }

    // setter, getter定義コールバック関数
    var accessPri = function(_pri) {
        return {
            set: function(value) {
                _pri = value;
            },
            get: function() {
                return _pri;
            }
        };
    }

    /**
     * メソッド methodA
     */
    var methodA = function(_pri) {
        return {
            value: function(arg1, arg2) {
               return this.prop1 + this.prop2 + arg1 + arg2 + this.pri;
           }
       };
    }

    return myClass;
})();

// MyClassオブジェクトを生成
var obj = new MyClass('aaa', 'bbb');
// priに値をセット
obj.pri = "hoge"
// MyClass.methodA() を呼び出す
var result = obj.methodA('ccc', 'ddd');

document.write(result);
</script>
8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8