【Javascript】 クラスの書き方

Javascriptでのクラスの書き方と扱いを紹介します。また、privateなフィールドやメソッドを作る方法も記載しています。

宣言

クラス宣言、フィールド、メソッドの一通りを含んだ例です。

class Person {
    constructor(name) {
        this.field1 = 100;
        this.name = name;

        // 自クラスのインスタンスのメソッド呼び出し
        this.method1();
    }

    method1() {
        this.field1 += 100;
        console.log("parent");
    }

    //...
}

let p = new Person("MyName");
console.log(p.name);
p.method1();
parent
MyName
parent

クラスのフィールドは、this.fieldnameのように、this.を付けて宣言します。コンストラクタ以外でも作成できます。

メソッドの宣言では、functionが不要です。

メンバは全てパブリックになります。そのため、プライベートにしたい場合はフィールド名の頭にアンダーバーを付けるなどの対策が一般的です。

デストラクタはありません。

staticなメンバ

クラスの直下にstaticを付けて配置します。メソッドはstaticを付けるだけですが、フィールドもクラスの直下に置きます。

class StaticSample {
    static staticProperty = "static property";
    static staticMethod() {
        return "static method";
    }

    constructor() {
        this.staticProperty = 300; // 名前は同じだが別の変数で, staticではない
    }
    
    hoge() {
        // 自身のクラス内でも ClassName.member の形式
        StaticSample.staticProperty = 100;
    }
}

// staticなメソッド
console.log(StaticSample.staticMethod());

// staticな値
console.log(StaticSample.staticProperty);
static method
static property

staticなメソッドや値は、ClassName.memberの形式で呼び出します。同名の普通の変数やメソッドを宣言した場合、別々の値として扱われます。

staticなため、すべてのインスタンスでただ1つの値を共有します。

継承

extendsを付けると継承が可能です.

class Person {/* ... */}

class Jhon extends Person {
    constructor() {
        // 親のコンストラクタ呼び出し
        super("Jhon");
    }

    // Personのmethod1をオーバーライド
    method1() {
        super.method1(); // 親クラスのmethod1を呼び出し

        this.field2 += 200;
        console.log("child");
    }

    method2() {
        // ...
    }
}

オーバーライド

メソッドのオーバーライドを行った場合、親クラスのオーバーライドされたメソッドを呼び出すにはsuper.method(args)のように書きます。

class Jhon extends Person {
    // ...

    // Personのmethod1をオーバーライド
    method1() {
        super.method1(); // 親クラスのmethod1を呼び出し

        this.field2 += 200;
        console.log("child");
    }
}

let j = new Jhon();
j.method1();
parent
child

親クラスのコンストラクタは、super(args);で呼び出します。

privateなメンバ

Symbolを利用して、一応privateなメンバは作成できます。

const Person = (() => {
    // 即時関数で囲まれているため、Symbolは1度だけ作成され、すべてのインスタンスで使い回される
    const _name = Symbol();
    const _setNameInner = Symbol();

    return class Person {
        constructor(name) {
            this[_name] = name; // 普通の代入
        }
    
        getName() {
            return this[_name];
        }

        setName(name) {
            this[_setNameInner](name); // privateメソッドの呼び出し
        }

        [_setNameInner](name) {
            this[_name] = name + " Inner";
        }
    }
})();

const p = new Person("Jhon");
const p2 = new Person("Jack");

console.log(p.getName());
console.log(p2.getName());

p.setName("Jhon2");
p2.setName("Jack2");

console.log(p.getName());
console.log(p2.getName());

クロージャを利用して外部から見えない値を作ることで、実質privateなメンバを作成します。

しかし、書き方が非常に違和感があるため、TypeScriptが使えない場合の最終手段とも言えます。

Symbolでなくても、この即時関数内でユニークな値にできるなら何でも良いです。ただ、それに最適なのがSymbolであるということです。

const _name = "a"; const _setNameInner = "b";でも正常に動きます。

その他仕様

  • アロー関数とfunctionでthisの扱いが異なる
  • interfaceやabstractに相当するものは無い
    jsdoc形式で書くのが精一杯
  • 多重継承はできない
  • メンバはいつでもどこでも作れる

特にinterfaceがないという点で、オブジェクト指向で書くことに限界があります。

そのため、大規模なアプリはTypeScriptで書くことを強く推奨します。

まとめ

Javascriptのクラスには制約があります。特にフィールドの作る場所がどこでも良いということもあり、とんでもないプログラムも書けてしまいます。interfaceはなく、privateなメンバも作りづらい使いづらいという状態で、あまり使い勝手はよくありません。

TypeScriptを利用しない場合、この制約を知った上でクラス設計をしていきましょう。

class

役に立ったらシェアしよう!