オブジェクトの作成過程

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

適当にオブジェクトの作成過程(new [クラス]の処理)について推察してみた

オブジェクト生成の前提

Objectクラスのインスタンスを生成するには

let obj = new Object();

当たり前の事だが、これが前提(基本)。

前稿のクラスの継承のメカニズム - プロトタイプチェイン - 愚鈍人でみたように、
'クラスの継承'に関わるプロトタイプチェインをたどるのに必要なObjectクラスの情報は

// クラスのnameプロパティにはクラス名が設定されている
console.log(Object.name); // Object

// 基底クラス(他のクラスを継承していないクラス)の__proto__プロパティはFunction.__proto__を指している
console.log(Object.__proto__ === Function.__proto__); // true
// Object.prototypeの内容は
console.log(Object.prototype);  

// クラスのprototypeプロパティのconstructorプロパティはクラス自身を指している。
console.log(Object.prototype.constructor === Object); // true
// Objectクラスはプロトタイプチェインの最後にあたるのでその先の継承元は存在しない
console.log(Object.prototype.__proto__ === null); // true

クラスを仕組みを理解するには、予備知識としてこれらの事を知っておく必要がある。

クラスオブジェクトの作成過程

コンストラクタ関数は普通のJavaScriptの関数

function Person(name, age) {
  this.name = name;
  this.age = age;
}

関数をクラスとして使う上で、慣例として関数名の先頭を大文字にして「クラスとして使うよ」という事を明示的に示す。

Objectクラスと同様に、Person関数に付随する値はどのようになっているかというと

// 関数のnameプロパティには関数名が設定されている
console.log(Person.name); // Person
// 関数の__proto__プロパティはFunction.__proto__を指している
console.log(Person.__proto__ === Function.__proto__);   // true

// Person.prototypeの内容は
console.log(Person.prototype);

// 関数のprototypeプロパティのconstructorプロパティは関数自身を指している。
console.log(Person.prototype.constructor === Person); // true
// Objectクラスと同じく、関数のprototypeプロパティの__proto__プロパティはnull
console.log(Person.prototype.__proto__ === Object.prototype); // true

関数を宣言すると、自動的に関数オブジェクトのプロトタイプチェインをたどる値が設定される事になる。

Person.name = "Person";
Person.__proto__ = Function.__proto__;
Person.prototype = new Object();
Person.prototype.constructor = Person;
Person.prototype.__proto__ = Object.prototype;

この事はおよそクラスには見えないどんな関数であっても、関数宣言はクラス宣言でもあって、その関数内のコードはクラスのコンストラクタの処理を示してもいる事になる。

つまりPerosn関数の宣言は、以下のPersonクラスの宣言と同義であると言える。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

Functionコンストラクター

Functionオブジェクトはクラスでもある。
Person関数はFunctionクラスのインスタンスという観点からすると、以下のコードと等価。

let person = new Function("name", "age", "this.name = name;this.age = age;");

但し、関数名を取得するとその名前は "anonymous"

console.log(Person.name); // anonymous

Functionクラスについての参考ドキュメントは

クラスのインスタンスを作成過程

new Personを使ってPerson関数(クラス)のインスタンスを作成すると、

let person = new Person("愚鈍人", 999);

インスタンスpersonのprotoプロパティにPerson関数のprototypeプロパティへの参照が設定される。

console.log(person.__proto__ === Person.prototype); // true

つまりnew Personのやっている事はオブジェクトを作成してprotoプロパティにクラスのprototypeプロパティを代入。
作成したオブジェクトをthis変数に代入して、コンストラクタ関数Personを実行する事(と推測される)。

これを、new Personではなくnew ObjectによりObjectクラスのインスタンスからPersonクラスのインスタンスを作成すると

let person = new Object();
person.__proto__ = Person.prototype;
Person.call(person, "愚鈍人", 999);

実際、このコードを使って作成したperson変数を確認してみると。

console.log(person instanceof Person);  // true
console.log(person);    // Person{name: '愚鈍人', age: 999}

Personクラスのインスタンスが生成されているようだ。

クラスの継承については

functionを使ってPersonクラスを継承するExPersonクラスを定義すると。

function ExPerson(name, age, gender) {
  Person.call(this, name, age)
  this.sex = gender;
}
// プロトタイプチェインを設定し直す
// Personクラスを継承するためにObjectクラスからの継承をPersonクラスへ変更
ExPerson.__proto__ = Person;  // 初期状態ではFunction.__proto__
ExPerson.prototype = new Person();  // 初期状態ではnew Object()
ExPerson.prototype.__proto__ = Person.prototype;  // 初期状態ではObject.prototype

このExPerson関数に対する処理をclassキーワードで表現すると

class ExPerson extends Person {
  constructor(name, age, gender) {
    super(name, age)
    this.sex = gender;
  }
}

クラスのextendsキーワードによる処理はExPerson関数のプロトタイプチェインを設定し直す一連の処理を自動的におこなってくれていると推察される。

ExPersonクラスのインスタンスを、Objectのインスタンスから作成すると、

let experson = new Object();
experson.__proto__ = ExPerson.prototype;
ExPerson.call(experson, "愚鈍人", 999, "man");

experson変数の中身は

console.log(experson);  // ExPerson{name: '愚鈍人', age: 999, sex: 'man'}

クラスのフィールドの継承を確認するためにPersonクラスのプロパティを設定

Person.clsField = "Person.clsField";

PersonクラスのプロパティもExPersonクラスに継承されている。

console.log(ExPerson.clsField); // Person.clsField

組み込みクラスでは

組み込みクラスでも同様にObjectのインスタンスから作成できるか、Arrayクラスで試してみる。

// let array = new Array()のかわり
let array = new Object();
array.__proto__ = Array.prototype;
Array.call(array);

// Arrayクラスのメソッドが実行可能
array.push(123);
array.push("abc");

// arrayは配列としてふるまい配列の要素としてアクセス可能
console.log(array, array[0], array[1]);
console.log(array instanceof Array);

new Array()でなくてもObjectのインスタンスからArrayクラスのインスタンスとしてふるまうオブジェクトを作成できるのが確認できる。

ページのトップへ戻る