プロトタイプベースにおけるプロパティの格納先とenumerable属性の値

《 初回公開:2022/11/17 , 最終更新:未 》

【 目次 】

クラスベースとプロトタイプベースのオブジェクト指向

classの定義はJavaScriptの既存のプロトタイプベースの継承に対する糖衣構文で

ECMAScript 2015 で導入された JavaScript のクラスは、主に JavaScript の既存のプロトタイプベースの継承に対する糖衣構文です。クラス構文は、JavaScript に新しいオブジェクト指向の継承モデルを導入するものではありません。

多くのオブジェクト定義でよく見られるパターンは、コンストラクタ内でプロパティを定義し、プロトタイプ上でメソッドを定義することです。
これにより、コンストラクタにはプロパティの定義のみが含まれ、メソッドは別のブロックに分割されるため、コードが読みやすくなります。

クラスのメンバーは何処に格納されているのか

プロトタイプベースのオブジェクト指向であるJavaScriptの場合、プロトタイプチェインをたどってアクセスできるプロパティを探すので、アクセスできるプロパティが何処に格納されているのか混乱してしまう。

クラスのメンバーは何処に格納されているのかについてはこの連載で、既に何回か触れているけれど、 これまでの復習のため整理してみる。

クラスのインスタンスのメンバー(プロパティ)

ここではクラスのインスタンスのメンバー(プロパティ)がどのオブジェクトに格納されているか調べてみる。

class SuperCls {
  constructor(_name) {
    this._name = name;
  }

  methodA() { }
  methodB() { }
  methodC() { }

  get name() {
    return name;
  }
}

class SubCls extends SuperCls {
  #private_var;
  constructor(name, age) {
    super(name); // スーパークラスのコンストラクターを呼び出し、name パラメータを渡す
    this._age = age;
  }

  // methodAメソッドをオーバーライド
  methodA() { }

  // スーパークラスの同名のメソッドを呼び出す
  methodB() {
    super.methodB();
    console.log(`${this.name}: SubClsクラスのmethodB`);
  }
}

let obj = new SubCls('愚鈍人', 999);

console.log("obj(SubClsのインスタンス): ");
for (let propName of Object.getOwnPropertyNames(obj))
  console.log("\t", propName, obj.propertyIsEnumerable(propName));

// obj.__proto__はSubCls.prototypeを参照していて
console.log(obj.__proto__ === SubCls.prototype);    // true

console.log("obj.__proto__(SubCls.prototype): ");
for (let propName of Object.getOwnPropertyNames(obj.__proto__))
  console.log("\t", propName, obj.__proto__.propertyIsEnumerable(propName));

// 更にSubCls.__proto__はSuperCls.prototypeを参照していて
console.log(SubCls.__proto__ === SuperCls.prototype);   // true

console.log("obj.__proto__.__proto__(SuperCls.prototype): ");
for (let propName of Object.getOwnPropertyNames(obj.__proto__.__proto__))
  console.log("\t", propName, obj.__proto__.__proto__.propertyIsEnumerable(propName));

実行結果

obj(SubClsのインスタンス): 
     _name true
     _age true
true
obj.__proto__(SubCls.prototype): 
     constructor false
     methodA false
     methodB false
false
obj.__proto__.__proto__(SuperCls.prototype): 
     constructor false
     methodA false
     methodB false
     methodC false
     name false

この結果からわかる事はクラスのインスタンス変数_nameと_ageはクラスのインスタンス自体に格納されている。
注目すべきは_age変数はSuperClsクラスで定義されているの関わらずSubClsのインスタンスに格納されている事。
SubClsに定義されているはずのプライベート変数#private_varはObject.getOwnPropertyNamesでも得る事ができない。

そして、クラスのSubClsのインスタンスメソッドmethodA,methodBそしてconstructorはクラスのprototypeプロパティすなわちobj.proto(SubCls.prototype)に格納されている。
さらに、継承されているスーパークラスのメソッドmethodA,methodB,methodCやゲッターnameやセッターはスーパクラスSuperClsのprototypeプロパティに格納されている事になる。

そして_nameや_ageのインスタンス変数のenumerable属性はtrueであるけれどそれ以外のメソッドのenumerable属性はfalseになっている。

それによりObject.getOwnPropertyNamesメソッドでは列挙されているクラスのメソッドが
for…in loopやObject.keysメソッドでは列挙されない事になる。

クラスのクラスの静的メンバー

同様にクラスの静的メンバー(プロパティ)がどのオブジェクトに格納されているかも調べてみる。

class SuperCls {
  static sa;

  static sMethod() { }
}

class SubCls extends SuperCls {
  static #sb;
  static sc;

  // 新規メソッドの追加
  static newsMethod() { }
}

console.log("SubCls: ");
for (let propName of Object.getOwnPropertyNames(SubCls))
  console.log("\t", propName, SubCls.propertyIsEnumerable(propName));

// SubCls.__proto__はSuperClsを参照していて
console.log(SubCls.__proto__ === SuperCls); // true

console.log("SubCls.__proto__(SuperCls): ");
for (let propName of Object.getOwnPropertyNames(SubCls.__proto__))
  console.log("\t", propName, SubCls.__proto__.propertyIsEnumerable(propName));

実行結果

SubCls: 
     length false
     name false
     prototype false
     newsMethod false
     sc true
true
SubCls.__proto__(SuperCls): 
     length false
     name false
     prototype false
     sMethod false
     sa true

クラスの静的プロパティは静的メンバー変数,静的メソッドに関わらずそれが宣言されているクラスに格納されている。
そして、静的メンバー変数のenumerable属性はtrueに,静的メソッドはfalseになっている。

インスタンスのプロパティとの違いとしては、インスタンスのメソッドがクラスのprototypeプロパティに格納されるのに対して、静的メソッドはクラスに格納される。
静的かどうかに関わらずメンバー変数のenumerable属性はtrueに、メソッドはenumerable属性はtrueになる。

ページのトップへ戻る