カプセル化クロージャサンプルコード変数関数・メソッド

【JavaScript】 class構文でのprivate変数定義のひな型パターン

更新日:2021/02/02

【JavaScript】 JSにおけるprivate変数と定義のひな型パターンで従来のオブジェクトインスタンスでのprivate変数定義について紹介したので、ここではclass構文での定義をお伝えします。

 

classコンストラクター内でのprivate変数

class構文でprivate変数を定義する場合、基本的にはconstructor内で変数と、その変数を参照するthisのメンバメソッドを定義します。


class c1 {
    constructor( v ) {
        let counter = v; // 外部から隠蔽されたprivate変数

        // メソッド追加
        this.up = function(){ counter ++;  return this;};
        this.down = function(){ counter --;  return this;};
        // ゲッターセッター追加
        Object.defineProperty(this,"count",{
            get:()=>counter,
            set:( val )=>{
                if( !Number.isFinite(val) )
                    throw new Error("数字ではない!!");
                counter = val;
            },
        });
        Object.freeze(this); // thisを凍結
    }
}
const obj = new c1( 100 );
console.log( obj.up().up().up().count ); // 103

▶注1:

thisは上書き禁止のため、次のようにオブジェクトリテラルを使用したプロパティ追加ができません。


this = {  // エラー:Uncaught SyntaxError: invalid assignment left-hand side
   up:function(){},
   down:function(){},
};

そのため、一つ一つプロパティを追加していく必要があります。

上書きでないのに、プロパティを追加できることに疑問を感じた人は、次のページを読んでみてください。
【JavaScript】 thisは上書き不可なのにプロパティ追加できる理由

▶注2:

ゲッターとセッターは、プロパティに直接追加できないため、Object.definePropertyメソッドを使用しています。

▶注3:

オブジェクトのプロパティはデフォルト状態では、上書き可能です。
そのため、メソッドを他のものに置き換えられる可能性があります。
これを防止するために、Object.freeze(this)でthis値を凍結しています。

ただしthisのメンバプロパティをpublicな変数として使用する場合、凍結すると変更ができなくなります。

また、このクラスをextendsでサブクラス化する可能性があるときは、サブクラス側のconstructorでthis値にプロパティ追加できなくなるので、Object.freeze(this)は実行しない方がいいです。

 

プロトタイプメソッドからprivate変数にアクセスする

上項のコードでthisにセットしたメソッドを、プロトタイプメソッドとして単純に置き換えてみます。

このコードは動作しません


class c1 {
    constructor( v ) {
        let counter = v; // 外部から隠蔽されたprivate変数
    }
    up(){
        counter ++;  return this;
    }
    down(){
        counter --;  return this;
    }
    get count(){
        return counter;
    }
    set count( cal ){
        if( !Number.isFinite(val) )
            throw new Error("数字ではない!!");
        counter = val;
    }
}

各メソッドで参照している変数counterは、constructor()内にあるので参照できません。
次のようにクラスの外に出せばアクセスできますが、複数のインスタンスを作成した場合、競合します。

それに今回は隠蔽が目的なので、別の方法を考える必要があります。

これではprivate変数になっていない


let counter = 0;
class c1 {
   constructor( v ) {
        counter = v;
    }
   ・・・
}

そこで次のように、this値と変数を結びつける仕組みを構築します。


const c1 = (()=>{
    const wMap = new WeakMap(); // (1) : WeakMapオブジェクトのインスタンス

    // (2) : (1) にデータをセット/取得するメソッド
    const getData = ( thisObj , prop ) => wMap.get(thisObj)[prop];
    const setData = ( thisObj , prop , val ) => wMap.get(thisObj)[prop]=val;

    return class {
        constructor( v ) {
            wMap.set( this , { counter : v } );
        }
        up(){
            setData( this ,"counter", getData(this,"counter") +1 );
            return this;
        }
        down(){
            setData( this ,"counter", getData(this,"counter") -1 );
            return this;
        }
        get count(){
            return getData( this ,"counter" );
        }
        set count( val ){
            if( !Number.isFinite(val) )
                throw new Error("数字ではない!!");
            setData( this ,"counter", val );
        }
    };
})();

const obj = new c1( 100 );
console.log( obj.up().up().up().count ); // 103

即時関数で、(1)と(2)をprivate変数として持つコンストラクター関数を作成して返しています。

(1)のWeakMapは、オブジェクトをキーとしてデータを所持できるオブジェクトです。
今回はthis値をキーとすることで、インスタンス毎にデータを所持することが可能となっています。

更新日:2021/02/02

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。