[EffectiveJavaScript輪読会4]prototype、getPrototypeOf、__proto__の違いを理解する

GAS

どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。

前回のおさらい

前回は、「非標準のスタック調査プロパティを使うのは避けよう」をお届けしました。

[EffectiveJavaScript輪読会4]非標準のスタック調査プロパティを使うのは避けよう
どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。前回のおさらい前回は、「関数のtoStringメ...

今回は、「prototype、getPrototypeOf、__proto__の違いを理解する」、「__proto__よりもObject.getPrototypeOfが好ましい」をお届けします。

いよいよ4章に突入ですね。

テキスト第4章「オブジェクトとプロトタイプ」の項目30、項目31に対応しています。

今日のアジェンダ

  • クラス構文以前のおさらい
  • プロトタイプのおさらい
  • __proto__プロパティ

クラス構文以前のおさらい

まずは、クラス構文以前は、どのようにしてクラスを表現していたのかのおさらいです。

function myFunction4_30_01() {

  /**
   * コンストラクタ 
   * @param {string} name 名前
   * @param {string} passwordHash ハッシュ
  */
  function User(name, passwordHash) {
    this.name = name;
    this.passwordHash = passwordHash;
  }

  //**自作メソッド(上書き) */
  User.prototype.toString = function () {
    return `[User ${this.name}]`;
  }

  //**パスワードをチェックするメソッド */
  User.prototype.checkPassword = function (password) {
    return hash(password) === this.passwordHash;
  }

  const u = new User('Tom', 'abcde');
  console.log(u);
  console.log(u.toString());
  console.log(u.checkPassword('abcde')); //ReferenceError: hash is not defined
}

プロトタイプのおさらい

ノンプロ研GAS中級講座で、プロトタイプを習いました。

メソッドは、クラスのprototypeプロパティに定義します。

クラスから生成されたインスタンスで、メソッドを呼び出すと、クラスのprototypeプロパティを参照します。

この仕組みのおかげで、インスタンスにすべてのメソッドを複製する必要がなくなりますし、メソッドがあちこちに散らばっているという不安もなくなります。

つまり、メソッドは常にクラスのprototypeプロパティに定義されている、ということを理解しておきましょう。

メソッドが、クラスのprototypeプロパティを参照する(メソッドを探しに行く)ことを、プロトタイプチェーンと呼びます。  引用元:ノンプロ研中級講座[GASコース]第1期クラス

プロトタイプオブジェクトの確認

それでは、オブジェクトのプロトタイプオブジェクトを確認してみましょう。

JavaScriptのには、オブジェクトのプロトタイプオブジェクトを取り出すためのメソッドが提供されています。

それが、Object.getPrototypeOf(対象となるオブジェクト)メソッドです。

Object.getPrototypeOf() メソッドは、指定されたオブジェクトのプロトタイプ (つまり、内部プロパティ [[Prototype]] の値) を返します。

インスタンスが参照してるプロトタイプチェーンと、クラスのデフォルトのプロトタイププロパティを比べてみましょう。

実行結果がtrueを返すことは、「そりゃそうだろ」という感じです。

function myFunction4_30_02() {

  /**
   * コンストラクタ 
   * @param {string} name 名前
   * @param {string} passwordHash ハッシュ
  */
  function User(name, passwordHash) {
    this.name = name;
    this.passwordHash = passwordHash;
  }

  //**自作メソッド(上書き) */
  User.prototype.toString = function () {
    return `[User ${this.name}]`;
  }

  //**パスワードをチェックするメソッド */
  User.prototype.checkPassword = function (password) {
    return hash(password) === this.passwordHash;
  }

  const u = new User('Tom', 'abcde');

  //オブジェクトのプロトタイプオブジェクトを取り出すためのメソッド
  //インスタンスが参照してるプロトタイプチェーンと、クラスのデフォルトのプロトタイププロパティを比べる
  console.log(Object.getPrototypeOf(u) === User.prototype); //true

}

__proto__プロパティ

プロパティのルックアップは、まずは、own propertyから探し始めるそうです。

自身のプロパティになければ、プロトタイプチェーンを辿って、クラスを参照にいく、という順番です。

この、オブジェクトのプロトタイプを取り出すための機構として、非標準で__proto__プロパティがありました。

アンダースコア2つは、「ダンダー」と呼ぶらしいです。

非標準だったが、あまりにもみんなが使うので、標準化し、非推奨となりました。

function myFunction4_30_03() {

  /**
   * コンストラクタ 
   * @param {string} name 名前
   * @param {string} passwordHash ハッシュ
  */
  function User(name, passwordHash) {
    this.name = name;
    this.passwordHash = passwordHash;
  }

  //**自作メソッド(上書き) */
  User.prototype.toString = function () {
    return `[User ${this.name}]`;
  }

  //**パスワードをチェックするメソッド */
  User.prototype.checkPassword = function (password) {
    return hash(password) === this.passwordHash;
  }

  const u = new User('Tom', 'abcde');
  //プロパティのルックアップは、まずは、own propertyから探し始めることが特徴らしいよ。

  //非推奨。オブジェクトのプロトタイプを取り出すための機構。(プロパティ)
  console.log(u.__proto__ === User.prototype); //true

}

デバッガを確認すると、プロトタイププロパティは、メソッドをもつインスタンスしか持たないように見えます。

※表示するのはプロトタイプチェーンをもつプロトタイププロパティのみなのかも?宿題。

しかし実は、すべてのオブジェクトに、プロトタイププロパティは存在するようです。

プロパティなので、in演算子でプロトタイプオブジェクトの有無を確認できます。

function myFunction4_31_01() {

  const test = {}; //オブジェクトにはプロトタイププロパティは存在する
  console.log('__proto__' in test); //true

  console.log(Object.getPrototypeOf(test)); //{}

  const empty = Object.create(null); //プロトタイプがないオブジェクト
  console.log('__proto__' in empty); //false(これがtrueになる環境がある)

}

テキストには「もし、Object.getPrototypeOfが実装されていなければ、__proto__プロパティを自作しちゃえ」というおまけもありました。

しかし、確認のしようがないので、写経したコードだけ貼っておきます。

function myFunction4_31_02() {

  const objectLiteral = {};

  const getMyPrototypeOf = (obj) => {
    if (typeof Object.getPrototypeOf === 'undefined') {
      Object.getPrototypeOf = function (obj) {
        let t = typeof obj;
        if (!obj || (t !== 'object' && t !== 'function')) {
          throw new TypeError('これはオブジェクトではありません');
        }
        return obj.__proto__;
      };
    }
  }

  getMyPrototypeOf(objectLiteral);
  console.log('__proto__' in objectLiteral); //true

}

旧IDEでは、__proto__プロパティを確認できますが、2021年に新IDEで廃止になりました。

現在は、prototypeプロパティで統一されています。

まとめ

以上で、「prototype、getPrototypeOf、__proto__の違いを理解する」、「__proto__よりもObject.getPrototypeOfが好ましい」をお届けしました。

テキストでは、改めてクラスの定義をしておりました。(無意味だとは思いますが咀嚼してみましょう)

クラスとは、コンストラクト関数を持つもの。メソッドを共有するためのプロトタイプオブジェクト(User.prototype)を組み合わせたもの。

だそうです。これを「プロパティ」と呼ばずに「オブジェクト」と呼ぶのは混乱必須ですね。

次回は、最終回で 「__proto__は決して変更しないこと」 をお届けします。

参考資料

Object.getPrototypeOf()

このシリーズの目次

タイトルとURLをコピーしました