🥄

JavaScriptのクラス構文はプロトタイプベースのシンタックスシュガーである

2023/01/22に公開

まえがき

対象者

  • JavaScriptにおいて、クラス構文についてしっかり学びたい方
  • クラス構文を使用しているが、その背後にある原理を理解したい方

本ページの目的

  • JavaScriptがプロトタイプベース言語であることやシンタックスシュガー(糖衣構文)についてザックリ理解
  • クラス構文=プロトタイプベースのオブジェクト指向プログラミング であることの理解

前提知識

シンタックスシュガー(糖衣構文)

プログラミングをやっていると一度は聞いたことがあるかもしれませんが、なじみもない方がいると思うので説明します。

シンタックスシュガーとはより読みやすく書きやすい構文にするための簡略化のことを指します。

シュガーがついているので料理に例えます。
料理において砂糖を使って料理を甘くできますが、砂糖は料理に必須のものではありません。同様に、シンタックスシュガーもプログラミングに必須のものではなく、それらを使わなくても同じことができますが、使うことでコードがより読みやすく書きやすくなります。

JavaScriptがプロトタイプベース言語であること

こちらもおさえておいたほうがいい内容なので説明します。プロトタイプベース言語とは、オブジェクト指向プログラミングで使われるスタイルの1つです。

  • 並列に並ぶスタイルとして、「クラスベース」や「アスペクト指向」が存在します。

  • JavaScriptでは以下のように既存のオブジェクトにprototypeを使って、メソッドを生やすことができます。
    以下ような書き方ができるものがプロトタイプベース言語の特徴です。

    function Person(name, age) {
      this.name = name;
    }
    
    Person.prototype.hello = function () {
      console.log("hello" + this.name); 
    };
    
    const bob = new Person("Bob", 18);
    bob.hello()
    

    こちらのコードを開発者ツール上で実行すると[[prototype]]のなかにhelloの関数が入っていることがわかります。

    image-20230122090503842

  • また、上のキャプチャを見ると[[prototype]]の中に[[prototype]]が存在しています。
    このようにオブジェクトが多重階層的につながっていることをプロトタイプチェーンといい、階層が浅いものから順々にプロパティが呼ばれます。

  • Wikipediaには

プロトタイプとは、複製元になったオブジェクトを意味しており、複製先のオブジェクトから見てそう呼ばれる。プロトタイプは同時にそのオブジェクトの暗黙の委譲先になり、これはプロトタイプを複製が継承していることと同じになる。

Wikipedia:プロトタイプベースより

プロトタイプベース言語を無理やり日本語に訳すと「複製元オブジェクトベース言語」みたいな感じでしょうか。

JavaScriptのクラス構文について

前提知識が長くなってしまいましたが、タイトル「JavaScriptのクラス構文はプロトタイプベースのシンタックスシュガーである」についての説明をします。
JavaScriptは前述したとおり、実際にはプロトタイプベースのオブジェクト指向プログラミング言語です。
ですが、より構造化されたコードを書くために、シンタックスシュガーとしてクラス構文を利用できます。

コードで確認

プロトタイプベース

まず、プロトタイプベースでPersonインスタンスを作成します。

function PersonProto(name, age) {
  this.name = name;
  this.age = age;
}
PersonProto.prototype.hello = function () {
  console.log("hello " + this.name);
};

const bobProto = new PersonProto("bob", 20);
console.log(bobProto);

結果は以下の画面キャプチャの通りです。

image-20230122094713961

このようにメソッドhello[[prototype]]に格納されています。

クラス構文

つぎに、クラスベースでPersonインスタンスを作成します。

class PersonClass {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  hello() {
    console.log("hello " + this.name);
  }
}

const bobClass = new PersonClass("bob", 20);
console.log(bobClass);

結果は以下キャプチャのようになっています。こちらもメソッドhello[[prototype]]に格納されていることがわかります。

image-20230122095045799

[[prototype]]同士が同じものをであることを確認しました。trueが出力されますね。

image-20230122095227146

この仕組みを知らないと発生するトラブルについて

このタイトルを理解していないと、以下の例のような事象が発生します。

例えば、次のように、「Person」という名前のクラスを定義して、あとからageプロパティを追加するとします。

class Person {
  constructor(name) {
    this.name = name;
  }
  hello() {
    console.log(
      `Hello, my name is ${this.name} and I am ${this.age} years old.`
    );
  }
}
const john = new Person("John");
john.age = 30; // プロパティをあとから追加
john.hello(); // Hello, my name is John and I am 30 years old.

この書き方は、プロトタイプベースであるからこそ後からageプロパティを追加できていますが、保守性が悪くなっています。
例えば、john.age = 30の記述場所を間違えた場合がundefined 出力されます。

class Person {
  constructor(name) {
    this.name = name;
  }
  hello() {
    console.log(
      `Hello, my name is ${this.name} and I am ${this.age} years old.`
    );
  }
}
const john = new Person("John");
john.hello(); // Hello, my name is John and I am undefined years old.
john.age = 30; 

このように、「JavaScriptのクラス構文はプロトタイプベースのシンタックスシュガーである」であることを考えるとJavaScriptのコーディングが捗るかもしれません。

GitHubで編集を提案

Discussion