JavaScriptのObject.getPrototypeOf():値の継承元を調べて動作の理由を追う
Object.getPrototypeOf()でオブジェクトの直接の継承元を調べる方法を解説。引数・戻り値、ブラウザーとNode.jsでの実行手順、期待出力、例外や参照共有の注意点を学べます。
目的:定義していない値が読める理由を調べる
オブジェクトに書いた覚えのないプロパティが読み取れたとき、確認したいのがプロトタイプです。JavaScriptでは、対象自身にプロパティが見つからない場合、プロトタイプを順にたどって探します。
Object.getPrototypeOf()は、その最初の参照先を取得する静的メソッドです。設定値がどこから来たのか、生成したオブジェクトが想定した継承元につながっているかを調べる場面に役立ちます。
この記事では、共通設定を継承するオブジェクトを用意し、継承元の確認と値の上書きを観察します。クラスの設計そのものよりも、手元のオブジェクトの構造を調査することが目的です。プロパティ探索の仕組みはMDNの継承とプロトタイプチェーンで説明されています。
動作環境と対応バージョン
Object.getPrototypeOf()はECMAScript 5で導入された標準機能です。MDNでは、2015年7月から主要ブラウザー間で広く利用できる機能とされています。現在のChrome、Edge、Firefox、Safariで利用できます。
ただし、APIの対応とサンプル全体の構文対応は別です。この記事のコードはconst、letなどのECMAScript 2015の構文も使うため、学習には現在のブラウザーを使ってください。仕様と対応状況はMDNのObject.getPrototypeOf()を参照できます。
Node.jsでも利用できます。本記事ではNode.js 22以上を実行対象とします。これはAPIの最小対応バージョンを示すものではありません。DOMや外部ライブラリに依存しないため、同じJavaScriptコードをブラウザーとNode.jsの両方で実行できます。Node.jsとJavaScript仕様の関係はNode.jsのECMAScript 2015以降の説明に整理されています。
引数と戻り値
呼び出し方は次のとおりです。
Object.getPrototypeOf(obj);objは、プロトタイプを調べる対象です。- 戻り値は、直接のプロトタイプであるオブジェクト、または
nullです。 - 対象や継承元をコピーする処理ではありません。
- 同期処理なので、通常の呼び出しに
awaitは不要です。
直接の継承元を1段だけ取得する
戻り値には、継承関係の全体が配列として入るわけではありません。さらに先を調べるには、戻されたオブジェクトに対してもう一度呼び出します。nullまで到達したら、それ以上のプロトタイプはありません。
また、obj.getPrototypeOf()という呼び出し方ではありません。対象を引数に渡してObject.getPrototypeOf(obj)と書きます。
プリミティブ値と例外
ECMAScript 2015以降では、文字列などのプリミティブ値はオブジェクトに変換して扱われます。例えば文字列を渡すと、通常はString.prototypeが返ります。ECMAScript 5時代のプリミティブ値の扱いとは異なります。
ただし、nullとundefinedは変換できず、TypeErrorになります。引数を省略した場合もundefinedを渡したのと同じです。入力元が不確かな処理では、この条件を確認してから呼び出してください。
コピーして試す:共通設定の参照先を確認する
次のコードは、外部通信や追加パッケージを使わずに動く例です。全体をまとめてコピーしてください。
(() => {
const defaults = {
theme: "light",
pageSize: 20
};
const settings = Object.create(defaults);
settings.owner = "SE-room";
const parent = Object.getPrototypeOf(settings);
console.log(`直接の継承元: ${parent === defaults}`);
console.log(`所有するキー: ${Object.keys(settings).join(",")}`);
console.log(`継承したテーマ: ${settings.theme}`);
settings.theme = "dark";
console.log(`上書き後: ${settings.theme}`);
console.log(`共通設定: ${parent.theme}`);
const grandparent = Object.getPrototypeOf(parent);
console.log(`次はObject.prototype: ${grandparent === Object.prototype}`);
console.log(`末端はnull: ${Object.getPrototypeOf(grandparent) === null}`);
const dictionary = Object.create(null);
console.log(`継承元なし: ${Object.getPrototypeOf(dictionary) === null}`);
})();ブラウザーでの実行方法
現在のブラウザーで空のタブなどを開き、開発者ツールのConsoleを選びます。上のJavaScript全体を入力して実行してください。ページの要素を操作する処理はありません。
外側の関数で変数の範囲を閉じているため、同じコードをもう一度実行しても、トップレベルのconstの再宣言による衝突を避けられます。
Node.jsでの実行方法
上のコードをUTF-8のprototype-demo.cjsとして保存します。ターミナルでそのファイルがあるフォルダーを開き、次のコマンドを実行します。
node --version
node prototype-demo.cjs最初のコマンドでバージョンを確認します。この記事の実行対象は22以上です。実行ファイルの指定方法はNode.jsのコマンドライン実行ガイドに沿っています。
期待出力
以下はコードと仕様から導いた期待出力です。ここで実機実行を行った結果ではありません。
直接の継承元: true
所有するキー: owner
継承したテーマ: light
上書き後: dark
共通設定: light
次はObject.prototype: true
末端はnull: true
継承元なし: truesettings自身が最初に持つキーはownerだけです。themeは継承元のdefaultsから読み取れます。その後、settings.themeへ代入すると、自身のプロパティが作られ、読み取りではその値が優先されます。この例のthemeは通常の書き込み可能なデータプロパティです。
失敗例と制約
nullを調べると例外になる
次の例は独立して実行できます。例外メッセージの文章は環境差があるため、例外名だけを表示します。
(() => {
for (const value of [null, undefined]) {
try {
Object.getPrototypeOf(value);
} catch (error) {
console.log(error.name);
}
}
})();期待出力は次のとおりです。
TypeError
TypeErrorチェーンを繰り返したどる場合も、nullを取得した後に再び呼び出さないよう終了条件を設けます。正常な終端として返るnullと、入力として渡して例外になるnullを区別してください。
戻り値を書き換えると共有元に影響する
メインの例でparent.theme = "dark"と書くと、defaults.themeが変わります。parentは同じオブジェクトへの参照だからです。継承元を確認する目的で取得したなら、安易に書き換えず、比較やログ表示に使うのが扱いやすいでしょう。
複数の設定オブジェクトが同じ共通設定を継承している場合、共有元の変更は、それぞれが自身の値で上書きしていないプロパティの読み取りに影響します。調査用コードがアプリケーションの状態を変えないよう注意してください。
万能な型判定や安全性検査にはならない
Object.getPrototypeOf(value) === Object.prototypeという比較だけで、あらゆる入力の種類を確定するのは避けます。例えば別のiframeで作られたオブジェクトでは、組み込みのプロトタイプ自体が現在の環境のものと異なります。
さらにProxyでは取得操作を捕捉でき、処理が例外を投げる場合もあります。外部から受け取った未知のオブジェクトを調べるなら、取得が必ず成功すると決めつけないでください。この制約はMDNのProxyのgetPrototypeOfトラップで確認できます。
活用事例と次の確認
このAPIは、自分で生成経路を管理しているオブジェクトの調査に向いています。共通設定の参照先が想定どおりか、辞書用オブジェクトの継承元がnullか、インスタンスが想定したプロトタイプにつながっているかを確認できます。
調査では、まず直接の継承元を取得して、期待したオブジェクトと===で比較します。値が自身になくても読める場合は、その参照先に同名の値があるかを確認します。必要な段数だけ進めると、問題の箇所を絞りやすくなります。
改善案として、通常の設定管理では継承を使う必要があるかも検討してください。独立した設定値が必要なら、生成時に値を明示して持たせる設計のほうが追いやすい場合があります。Object.getPrototypeOf()は、継承を増やすためではなく、現在の構造を理解して設計判断につなげる道具として使えます。
公式参考資料
Googleの優先するニュース提供元にSEの部屋を追加
Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。
候補にSEの部屋が表示されない場合は、まだ追加できません。