LoginSignup
1
4

More than 5 years have passed since last update.

JavaScript/TypeScript クラス変数スコープ色々

Last updated at Posted at 2016-11-11

TypeScriptやES6ではclass構文が使用可能となっている。
しかし、そのクラスには物足りなさを感じることが多い。
クラス変数は全てthisで紐づけられPublicな状態となる。
TypeScriptではPrivateの定義もできるけど、出てくるコードはPublicの時と一切変わらない。

TypeScript/Class
function main(){
    const T=new Test("T");
    put(T);
    T.put();

    const T2=new Test("T2");
    put(T2);
    T2.put();

    put(T);
    T.put();
}

function put(T){
    console.log("\n- global.put()@"+T.instanceName);
    console.log(T.dfScp);       //private? -> アクセス可能
    console.log(T.prv);         //private? -> アクセス可能
    console.log(T.pub);         //public
}

class Test{
    public instanceName;
    dfScp;
    private prv;
    public pub;

    constructor(instanceName){
        this.instanceName=instanceName;
        this.dfScp="defoult scorp@"+instanceName;
        this.prv="private@"+instanceName;
        this.pub="public@"+instanceName;
    }
    public put(){
        console.log("\n- Test.put()@"+this.instanceName);
        console.log(this.dfScp);
        console.log(this.prv);
        console.log(this.pub);
    }
}

main();
出力

- global.put()@T
defoult scorp@T
private@T
public@T

- Test.put()@T
defoult scorp@T
private@T
public@T

- global.put()@T2
defoult scorp@T2
private@T2
public@T2

- Test.put()@T2
defoult scorp@T2
private@T2
public@T2

- global.put()@T
defoult scorp@T
private@T
public@T

- Test.put()@T
defoult scorp@T
private@T
public@T

動作結果の通りどのスコープから呼んでもPrivate、Public関係なく結果が返ってくる。
ついでにStatic的な変数が使用できない。

ここでスコープ見本としてC#でPrivate、Public、Static Private、Static Publicを定義して同じように別スコープから呼んでみる。

C#/Class
using System;

class Program{
    static void Main(){
        Test T=new Test("T");
        put(T);
        T.put();

        Test T2=new Test("T2");
        put(T2);
        T2.put();

        put(T);
        T.put();

    }

    static private void put(Test T){
        Console.WriteLine("\n- Main.put()@"+T.instanceName);
        //Console.WriteLine(T.dfScp);       //private -> アクセス不可
        //Console.WriteLine(T.prv);         //private -> アクセス不可
        Console.WriteLine(T.pub);           //public
        //Console.WriteLine(Test.sprv);     //private -> アクセス不可
        Console.WriteLine(Test.spub);       //public
    }
}

class Test{
    public string instanceName;
    string dfScp;
    private string prv;
    public string pub;
    static private string sprv;
    static public string spub;

    public Test(string instanceName){
        this.instanceName=instanceName;
        this.dfScp="defoult scorp@"+instanceName;
        this.prv="private@"+instanceName;
        this.pub="public@"+instanceName;
        Test.sprv="static private@"+instanceName;
        Test.spub="static public@"+instanceName;
        // this・Testは省略可
    }
    public void put(){
        Console.WriteLine("\n- Test.put()@"+instanceName);
        Console.WriteLine(dfScp);
        Console.WriteLine(prv);
        Console.WriteLine(pub);
        Console.WriteLine(sprv);
        Console.WriteLine(spub);
    }
}
出力

- Main.put()@T
public@T
static public@T

- Test.put()@T
defoult scorp@T
private@T
public@T
static private@T
static public@T

- Main.put()@T2
public@T2
static public@T2

- Test.put()@T2
defoult scorp@T2
private@T2
public@T2
static private@T2
static public@T2

- Main.put()@T
public@T
static public@T2

- Test.put()@T
defoult scorp@T
private@T
public@T
static private@T2
static public@T2

結果を見るとPrivateもPublicもStaticも機能している。
Staticな変数はインスタンス(newしたクラス)に依存せず、最後に代入された値を示している。
また、インスタンス名(T、T2)からのアクセスは不可。
VBのモジュールなんかは全部がstaticなクラスみたいなイメージかな?

ここで僕がいつもやっている疑似クラスでjsを書いてみる。

funClass
function main(){
    const T=new Test("T");
    put(T);
    T.put();

    const T2=new Test("T2");
    put(T2);
    T2.put();

    put(T);
    T.put();
}

function put(T){
    console.log("\n- global.put()@"+T.instanceName);
    console.log(T.dfScp);       //private -> undefined
    console.log(T.prv);         //private -> undefined
    console.log(T.pub);         //public
    console.log(Test.spub);     //stutic public
}

function Test(instanceName){
    this.instanceName;
    var dfScp;  //default
    var prv;    //private
    this.pub;
    Test.spub;  //static public

    const constructor=()=>{
        this.instanceName=instanceName;
        dfScp="defoult scorp@"+instanceName;
        prv="private@"+instanceName;
        this.pub="public@"+instanceName;
        Test.spub="static public@"+instanceName;
    }
    this.put=function(){
        console.log("\n- Test.put()@"+this.instanceName);
        console.log(dfScp);
        console.log(prv);
        console.log(this.pub);
        console.log(Test.spub);
    }
    return constructor();
}

main();
出力

- global.put()@T
undefined
undefined
public@T
static public@T

- Test.put()@T
defoult scorp@T
private@T
public@T
static public@T

- global.put()@T2
undefined
undefined
public@T2
static public@T2

- Test.put()@T2
defoult scorp@T2
private@T2
public@T2
static public@T2

- global.put()@T
undefined
undefined
public@T
static public@T2

- Test.put()@T
defoult scorp@T
private@T
public@T
static public@T2

Static Private以外のスコープが全て使用可能。
ちなみにこれのイメージとしては一つのコンストラクタに何もかも詰め込んでいる感じになるらしく、
結果、初期化リソースが多くなってメモリ的にあまり宜しくないらしい。

JavaScriptはプロトタイプベースと言われるようにクラスの構築はprototypeで分散させるように書いた方が良いみたい。
色々やった結果TypeScriptのClassをコンパイルしたものに色々なスコープを対応できるように手を加えるのが良さそうだった。

ProtoClass
function main(){
    const T=new Test("T");
    put(T);
    T.put();

    const T2=new Test("T2");
    put(T2);
    T2.put();

    put(T);
    T.put();
}

function put(T){
    console.log("\n- global.put()@"+T.instanceName);
    //console.log(dfScp);
    //console.log(this[prv1]);      // -> Error not defined
    console.log(this[Symbol()]);    // -> undefined
    //console.log(p.get(this).prv2);// -> Error not defined
    //console.log(this[prv3]);      // -> Error not defined
    //console.log(sprv);            // -> Error not defined
    console.log(this.pub);
    console.log(Test.spub);
}

const Test=((/*class*/)=>{
    const p=new WeakMap();
    //this.instanceName; this宣言前
    var dfScp;  //default
    const prv1=Symbol();    //private Symbol
    //prv2;             //private WeakMap
    const prv3=Math.random();//private? random
    //this.pub; this宣言前
    var sprv    //static private
    //Test.spub;    //static public

    function Test(instanceName){
        p.set(this,{});
        this.instanceName=instanceName;
        dfScp="defoult scorp@"+instanceName;
        this[prv1]="private Symbol@"+instanceName;
        p.get(this).prv2="private WeakMap@"+instanceName;
        this[prv3]="private? random@"+instanceName;
        this.pub="public@"+instanceName;
        sprv="static private@"+instanceName;
        Test.spub="static public@"+instanceName;
    }
    Test.prototype.put=function(){
        console.log("\n- Test.put()@"+this.instanceName);
        console.log(dfScp);
        console.log(this[prv1]);
        console.log(p.get(this).prv2);
        console.log(this[prv3]);
        console.log(this.pub);
        console.log(sprv);
        console.log(Test.spub);
    }
    return Test;
})();

main();
出力

- global.put()@T
undefined
undefined
static public@T

- Test.put()@T
defoult scorp@T
private Symbol@T
private WeakMap@T
private? random@T
public@T
static private@T
static public@T

- global.put()@T2
undefined
undefined
static public@T2

- Test.put()@T2
defoult scorp@T2
private Symbol@T2
private WeakMap@T2
private? random@T2
public@T2
static private@T2
static public@T2

- global.put()@T
undefined
undefined
static public@T2

- Test.put()@T
defoult scorp@T2
private Symbol@T
private WeakMap@T
private? random@T
public@T
static private@T2
static public@T2

ちなみにprototypeを使用するとクラスでPrivateな変数を定義するには工夫が必要になる。
そこでClassと同じくES6から追加されているSymbolやWeakMap等を使用するとまさしくPrivateに出来るんだそう。
ちなみにSymbolは

const hoge=Symbol();

のように呼びだすと文字列でない(Symbol型という新規の型定義)固有のキーを発行できるみたい。
固有なのでもう一度同じように定義しても等価にはならない。

const hoge=Symbol();
const huga=Symbol();
console.log(hoge==huga);
// -> false

これをオブジェクトのキーにすることでアクセスを制限する仕組み。

const Test=(()=>{
    //private vars
    const hoge=Symbol();
    const huga=Symbol();
    const piyo=Symbol();

    function Test(instanceName){
        this[hoge]="hoge";
        this[huga]="huga";
        this[piyo]="piyo";
        console.log(this[hoge]);
        // -> hoge
        console.log(this[huga]);
        // -> huga
        console.log(this[piyo]);
        // -> piyo
    }
    return Test;
})();

他にもいろいろあるみたいだけどそれは省略。

固有なキーさえあれば良いっていうなら乱数をキーにしても行けそうな気もするんだけどどうなんだろうか。

const rnd=Math.random();
this[rnd]="rnd";

約数百万分の一とはいえキーが重複する可能性もありえるけど。

次にWeakMapについて。
これはthisなどのオブジェクトをキーに値を登録できるテーブル的なもの、かな。

const Test=(()=>{
    const p=new WeakMap();
    function Test(){
        p.set(this,"hoge");
        console.log(p.get(this));
        // -> hoge
    }
    return Test
})();

このWeakMap pを関数スコープ閉じ込めて外からアクセスできないようにすればPrivateの出来上がり。
ただ、このままだとthisしかキーに出来ないので、

const Test=(()=>{
    const p=new WeakMap();
    function Test(){
        p.set(this,{});
        console.log(p.get(this));
        // -> {}

        p.get(this).hoge="hoge";
        p.get(this).huga="huga";
        console.log(p.get(this).hoge);
        // -> hoge
        console.log(p.get(this).huga);
        // -> huga
    }
    return Test
})();

こうしてやると良い感じ。

以上。

1
4
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
1
4