クラス構文

【JavaScript】 クラス定義でプライベートプロパティが使用できるようになったと聞いて確認してみた

更新日:2022/01/27

JavaScriptのオブジェクトにプライベートなプロパティが実装されることを待ち望んでいる人が多く存在していました。
その声に押されたのか、プライベートプロパティが実装されるようです。

そこでプライベートプロパティについて解説してみます。

 

ECMAScript2022でのclass定義

ECMAScript2022のドラフトを確認したところ、class定義で次の二つの機能が追加されています。

  1. プライベートプロパティ(プライベートフィールド/メソッド)
  2. クラスプロパティ(クラスフィールド)
  3. 静的プロパティ/静的プライベートプロパティ
  4. 静的ブロック

今回はドラフトをざっと追ってある程度理解しましたが、完全に読み解くには時間的余裕がありません。
しかし幸いにも、FirefoxとChromeの最新バージョン(2020年1月現在)には既に組み込まれているようなので、こちらで動作を確認しました。

 

プライベートプロパティ(プライベートフィールド/メソッド)

プライベートプロパティは、class定義のみでおこなえます。
オブジェクトでは定義できません。

class定義については、次の記事で紹介しています。
【JavaScript】 JSにおけるクラスとは?正体についても調べてみた

プライベートプロパティの定義は、プロパティ名の前に"#"を付けます。
通常のプロパティだけでなくメソッドやゲッター/セッターでも利用可能です。


class a {
    #propA = 0;
    constructor( v ) {
        this.#prop = v;
    }
    set #prop( v ){
        this.#propA = v;
    }
    get #prop(  ){
        return this.#propA;
    }
    #print(){  // 
        console.log( this.#propA );
    }
    func(){
       this.#print();
    }
}

実行するときは、new演算子でインスタンスを作成してパブリックなメソッドを呼び出します。


const b = new a( 200 );
b.func(); // 200

プライベートプロパティはclass定義のメソッド内でthisキーワード経由でアクセスします。
外部からアクセスするとエラーがスローされます。
これはコードの実行時ではなくて、コード読み込み時にエラーになります。


b.#print(); // Uncaught SyntaxError: reference to undeclared private field or method #print

extends指定したクラスのプライベートプロパティも、直接呼び出すことができません。


class a2 {
    #propA2 = 100;
    get prop(){ return this.#propA2; }
}
class a extends a2{
    constructor(  ) { super();}
    func(){
       console.log( super.prop ); // 100
       console.log( super.#propA2 ); // Uncaught SyntaxError: invalid access of private field on 'super'
    }
}

上記のコードは、コード解釈時に super.#propA2 でエラーになるので、実際にはsuper.propの参照前に停止します。

 

クラスプロパティ(クラスフィールド)

以前のclass定義は、インスタンスのプロパティをconstructor内でセットしていました。

class a {
    constructor(  ) {
        this.prop1 = 1;
        this.prop2 = "abc";
    }
}
const b = new a();
console.log( b ); // Object { prop1: 1, prop2: "abc" }

次のように、class定義として記述できるようになりました。

class a {
    prop1 = 1;
    prop2 = "abc";
}
const b = new a();
console.log( b ); // Object { prop1: 1, prop2: "abc" }

メソッドの定義については、少し注意が必要です。
次のコードの二つのメソッド定義は、同じものではありません。

class a {
    prop1(){
        console.log( "prop1" );
    };
    prop2 = function(){
        console.log( "prop2" );
    };
}
a.prototype.prop1(); // "prop1"
const b = new a();
b.prop1();  // "prop1"
console.log( b ); // Object { prop2: prop2() }

prop1は以前からある記述方法です。
class定義を解釈して生成されるfunctionオブジェクトのprototypeプロパティのメンバーにprop1が追加されます。
そしてインスタンスのプロトタイプチェーンに組み込まれます。

prop2は、1や"abc"などのプリミティブをセットした場合と同じです。
インスタンスのメンバーとして追加されます。

 

静的プロパティ/静的プライベートプロパティ

以前のclass定義は、staticキーワードと共にメソッドを定義することで、静的メソッドを定義できました。

class a {
    static staticMethod(){
        console.log( "staticMethod" );
    }
}
a.staticMethod(); // "staticMethod"

staticはメソッドのみでフィールドには使用できませんでしたが、今回の機能追加で可能になりました。

class a {
    static staticField = 100;
}
console.log( a.staticField ); // 100

プライベートプロパティやゲッター/セッターも静的な配置が可能です。

class a {
    static #staticPrivateField = 100;
    static get #staticPrivatefunc(){ return this.#staticPrivateField; }
    static set #staticPrivatefunc( v ){ this.#staticPrivateField = v; }
    static func(){ console.log( this.#staticPrivatefunc ); }
}
console.log( a.func ); // 100

 

静的ブロック

今回のアップデートではclass定義に静的ブロックが追加されました。
静的ブロックは staticキーワードで定義します。

これまでもclass定義は staticキーワードが存在していました。
これは、次のようにプロパティやメソッドを対象にしたものです。
次のように、インスタンス化しなくても直接アクセスできます。


class a {
    static prop=100;
    static func(){
       console.log( this.prop );
    }
}

a.func(); // 100

一方、静的ブロックは次のようにstaticキーワードの後に、{ }で処理を記述します。
記述した処理は、そのまま実行されます。
そのため、静的プロパティの初期化などに使用できます。


class a {
    static prop;
    static {
       this.prop = 200;
    }
}

console.log( a.prop ); // 200

上記のコードは、次のコードと同等です。


class a {}
a.prop = 200;

console.log( a.prop ); // 200

ただし、プライベート変数が関連すると置き換えることができません。


class a {
    static #prop;
    static {
       this.#prop = 200;
    }
}

上のコードのプライベート変数を、外部から変更しようとするとエラーになります。


class a {}
a.#prop = 200; // Uncaught SyntaxError: reference to undeclared private field or method #prop

 

まとめ

これまでclass定義は既存の手法の糖衣構文でしかなく、しかも、一般的なクラス概念からすると少し違和感があるものでした。
そのようなことから、僕のように『使用したくない!』という頑固者も多く存在します。

しかしプライベートプロパティの導入により、class定義を使用する動機が出てきました。

でも納得がいかないのは僕だけでしょうか。

JavaScriptのオブジェクトは、オブジェクトリテラルで作成されることが多いです。
JavaScriptの言語としての機能性から言えば、オブジェクトリテラルにプライベート要素を導入した方がメリットが大きいです。
ECMAScript2022の仕様を見ると、それほど大きな変更なくオブジェクトリテラルに導入できるのに、今回は導入されませんでした。

クラスの目的は、オブジェクトを作成することです。
オブジェクトリテラルもオブジェクトを作成するものです。
同じオブジェクトを作成するものなのに機能差がでるのは、なんだかチグハグな印象を受けますね。

class定義を使わせたいという意図があるのだと思いますが、それに固執しすぎな気がします。

とても、残念です。

あとプライベート要素が"#"で始まるのは、一目でプライベートと判断できるので便利。
しかし変数名に意味を持たせてしまったのは、言語としての一貫性が損なわれてしまった。
これをやるなら、もっと以前に、例えばconstやletではなくてなんらかの記号で始まるような仕様にできたはずである。
別に不満なわけではないけれど、なんだかモヤモヤするのです。

更新日:2022/01/27

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

スポンサーリンク

記事の内容について

null

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

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

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

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

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

 

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