【初心者】ECMAScrip、TypeScript には class 定義 があるが、プロトタイプチェーンはおさえておこう

オウルです。

ECMAScript 2015class 定義が導入されましたが、これはシンタックスシュガーであり、javascript はプロトタイプベースです。

とはいっても class 定義で実装する機会が増えるでしょうし、TypeScript とか使うとさらにプロトタイプを意識することが少なくなりそうな気がしています。

とはいっても javascript を使用する以上、プロトタイプは頻出するので、「プロトタイプ??」っていう人向けに基本のおさらいです。

頻出用語

javascript プロトタイプを理解する上で、頻出する用語と、その用語の端的な説明です。

グローバルオブジェクト

ブラウザー内では window オブジェクトが、グローバルオブジェクトです。グローバル変数や関数はすべて、 window オブジェクトのプロパティとしてアクセスすることができます。

Object

javascript のすべてのオブジェクトは Object に由来します。

Function Object

function 宣言で作成された関数は Function オブジェクトであり、Function オブジェクトの全てのプロパティ、メソッド、振る舞いを持ちます。

javascript では、function(関数)は Function オブジェクトと呼び、オブジェクトとして扱われます。

コンストラクタ

javascript プロトタイプを理解を妨げる紛らわしいものの1つであるコンストラクタについてです。コンストラクタがつく用語が複数あるので整理します。

コンストラクタ関数

オブジェクトを作成および初期化する関数オブジェクトです。Java や C# でいうコンストラクタと同義です。

Object コンストラクター

Object コンストラクターは、%Object%組み込みオブジェクトであり、グローバルオブジェクトのObjectプロパティの初期値です。

Function コンストラクター

Function コンストラクターは、%Function%組み込みオブジェクトであり、グローバルオブジェクトの Function プロパティの初期値です。

コンストラクタ関数と関数宣言の違い

Function コンストラクタによる関数の生成は、生成コンテキストにクロージャを作りません。つまり常にグローバルスコープで作成します。

constructorプロパティ

オブジェクトのコンストラクタ関数への参照です。

class 定義のコンストラクタメソッド

ECMAScript 2019 class 定義 を参照。

prototype と __proto__([[Prototype]])の違い

まず最初に、Chrome DevTools で適当にオブジェクトを作成します。すると、__proto__ という奇妙なプロパティが現れます。

次に someObject.[[Prototype]] です。これは記法で、 someObject のプロトタイプを示します。つまり JavaScript の __proto__ プロパティ(現在は非推奨)と同等(someObject.[[Prototype]] = __proto__)です。それでは、__proto__ が一体何者なのか見ていきましょう。

__proto__

ECMAScript 2015 の仕様を見ます。

All ordinary objects have an internal slot called [[Prototype]]. The value of this internal slot is either null or an object and is used for implementing inheritance. Data properties of the [[Prototype]] object are inherited (are visible as properties of the child object) for the purposes of get access, but not for set access. Accessor properties are inherited for both get access and set access.

機械翻訳:

すべての通常のオブジェクトには、[[Prototype]]と呼ばれる内部スロットがあります。この内部スロットの値はnullまたはオブジェクトのいずれかであり、継承の実装に使用されます。 [[Prototype]]オブジェクトのデータプロパティは、取得アクセスの目的で継承されます(子オブジェクトのプロパティとして表示されます)が、セットアクセスではありません。アクセサプロパティは、取得アクセスと設定アクセスの両方で継承されます。

要約すると、「someObject.[[Prototype]] = __proto__」であること、「__proto__ は、継承元の prototype(後続で説明) 」である。

ここで、少しでもプログラミング経験があれば、お馴染みの「継承」という用語が登場しました。javascript では、この継承(prototype)のつながりのことを、プロトタイプチェーンと呼びます。

次は「prototype」です。ここで注意点が1つあります。ECMAScript 2015 から、__proto__([[Prototype]]) は、 Object.getPrototypeOf() と Object.setPrototypeOf() のアクセサを使ってアクセスします(現在 __proto__ プロパティの直アクセスは非推奨です)。但し、説明コードは可読性を考慮して__proto__で記載します。

プロトタイプ(prototype)

他のオブジェクトの共有プロパティを提供するオブジェクトです。

Object.prototype には、is, create メソッドはありません。継承元にないので、当然、 obj にも存在しません。

プロトタイプとコンストラクタ

各コンストラクターは、プロトタイプベースの継承と共有プロパティの実装に使用される prototype プロパティを持つ関数です。

プロトタイプチェーン

Object プロトタイプチェーン

継承された toString()

この toString() メソッドは誰のメソッドでしょうか?

hasOwnProperty() メソッドは、オブジェクトが指定されたプロパティを持っているかどうかを示す真偽値を返します。

プロトタイプチェーンの末尾

Object は 実は Function から生成

どこでチェーンが切れるか

まとめ

prototype と __proto__ の正体は分かってもらえたでしょうか?感のするどい人は、「存在しないプロパティへのアクセスは、プロトタイプチェーン全体を通過する⇒パフォーマンス低下」みたいなことにも気づいたかもしれませんね。もっと prototype について知りたいと思った方は、MDN を見てください。とても勉強になりますよ。

ただ、個人的には冒頭にも書きましたが、パフォーマンスやメモリに気を付けながら ECMAScrip 2015~、TypeScript のクラス定義で実装すると思います。babel や webpack を上手く使えば、レガシーブラウザでも動作しますしね。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA