MochiuWiki : SUSE, EC, PCB
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
JavaScriptの基礎 - 継承とプロトタイプのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
JavaScriptの基礎 - 継承とプロトタイプ
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == JavaScriptはプロトタイプベースのオブジェクト指向言語であり、全ての継承はプロトタイプチェーンという仕組みによって実現されている。<br> ES2015で導入された <code>class</code> 構文の <code>extends</code> と <code>super</code> により、他の言語に近い直感的なクラス継承を記述できるが、<br> これはシンタックスシュガーである。(内部では依然としてプロトタイプベースの機構が動作している)<br> <br> プロトタイプチェーンとは、オブジェクトが持つ内部プロパティ <code><nowiki>[[Prototype]]</nowiki></code> を辿ることで、プロパティやメソッドを継承元から順に探索する仕組みである。<br> プロパティは、<u>自身のオブジェクト -> プロトタイプ -> プロトタイプのプロトタイプ -> ... -> null</u> の順に探索され、見つからない場合は <u>undefined</u> を返す。<br> <br> 型チェックには <code>instanceof</code> と <code>typeof</code> の2つの演算子を使い分ける。<br> <code>typeof</code> はプリミティブ型の判定に用い、<code>instanceof</code> はプロトタイプチェーンを辿ってオブジェクトの型 (コンストラクタ) を検査する。<br> <br> <u>なお、<code>typeof null === "object"</code> となる点は言語仕様上の既知の問題であり、注意が必要である。</u><br> <br> ReactのErrorBoundaryは、クラス継承が今なお必須とされる代表的な実用例である。<br> <code>getDerivedStateFromError</code> と <code>componentDidCatch</code> はクラスコンポーネント専用のライフサイクルメソッドであり、現時点ではHooksに相当する代替手段が存在しない。<br> <br> クラスの宣言、コンストラクタ、メソッド、フィールド、静的メンバーについては、[[JavaScriptの基礎 - クラス]]のページを参照すること。<br> <br><br> == クラスの継承 == <code>extends</code> キーワードを使用することにより、既存のクラスを継承した新しいクラスを定義できる。<br> 子クラスは親クラスのプロパティとメソッドを全て引き継ぎ、独自のメンバーを追加または上書きすることができる。<br> <br> ==== extendsキーワード ==== <u>class Child extends Parent {}</u> と記述することにより、<u>Child</u> は <u>Parent</u> を継承する。<br> <br> 子クラスのインスタンスは、親クラスで定義された全てのプロパティとメソッドを利用できる。<br> <br> <code>extends</code> を使用した継承の基本例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + " が鳴いた"); } } // DogはAnimalを継承する class Dog extends Animal { constructor(name, breed) { super(name); // 親クラスのコンストラクタを呼び出す this.breed = breed; // Dog固有のプロパティを追加する } // Dog 固有のメソッドを追加する fetch() { console.log(this.name + " がボールを取ってきた"); } } const dog = new Dog("ポチ", "柴犬"); dog.speak(); // "ポチ が鳴いた" (親クラスのメソッドを使用できる) dog.fetch(); // "ポチ がボールを取ってきた" console.log(dog.name); // "ポチ" console.log(dog.breed); // "柴犬" </syntaxhighlight> <br> ==== superキーワード ==== <code>super</code> キーワードには、コンストラクタ内とメソッド内の2つの用途がある。<br> <br> * コンストラクタ内の <code>super()</code> *: 親クラスのコンストラクタを呼び出す。 *: <code>this</code> にアクセスするより前に呼び出す必要があり、省略すると <u>ReferenceError</u> が発生する。 *: <br> * メソッド内の <code>super.method()</code> *: 親クラスのメソッドを呼び出す。 *: 子クラスのメソッド内で親の処理を実行してから拡張する場合に使用する。 <br> <code>super()</code> と <code>super.method()</code> の両方を示す例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> class Vehicle { constructor(make, speed) { this.make = make; this.speed = speed; } describe() { return this.make + " (最高速度: " + this.speed + " km/h)"; } } class ElectricCar extends Vehicle { constructor(make, speed, range) { super(make, speed); // 親コンストラクタを呼び出してから this にアクセスする this.range = range; // ElectricCar 固有のプロパティ } describe() { // super.describe() で親のメソッドを呼び出してから拡張する return super.describe() + " / 航続距離: " + this.range + " km"; } } const ev = new ElectricCar("BEV", 200, 500); console.log(ev.describe()); // "BEV (最高速度: 200 km/h) / 航続距離: 500 km" </syntaxhighlight> <br> ==== メソッドのオーバーライド ==== 子クラスで親クラスと同名のメソッドを定義すると、親のメソッドがオーバーライド (上書き) される。<br> <code>super.method()</code> を呼び出してから処理を追加することにより、親の実装を再利用しつつ拡張することができる。<br> <br> オーバーライドの例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> class Logger { log(message) { console.log(message); } } // Loggerを継承してタイムスタンプ付きのログに拡張する class TimestampedLogger extends Logger { log(message) { // 親のlogを呼び出してから拡張する const timestamp = new Date().toISOString(); super.log("[" + timestamp + "] " + message); } } const logger = new TimestampedLogger(); logger.log("アプリケーションが起動しました"); // "[2026-02-19T00:00:00.000Z] アプリケーションが起動しました" </syntaxhighlight> <br><br> == プロトタイプチェーン == プロトタイプチェーンは、JavaScriptのプロトタイプベースの継承の核心となる仕組みである。<br> <code>class</code> 構文が導入された後も、継承の内部メカニズムはプロトタイプチェーンによって実現されている。<br> <br> この仕組みを理解することで、JavaScriptのオブジェクトモデルをより深く把握できる。<br> <br> ==== プロトタイプの基本概念 ==== 全てのJavaScriptオブジェクトは、内部プロパティ <code><nowiki>[[Prototype]]</nowiki></code> を持つ。<br> この内部プロパティは、<code>Object.getPrototypeOf()</code> を使用して取得できる。<br> <br> 非推奨の <code>__proto__</code> プロパティも同様の機能を持つが、コードでは <code>Object.getPrototypeOf()</code> の使用を推奨する。<br> <br> プロトタイプチェーンの終端は <u>null</u> であり、<code>Object.prototype</code> のプロトタイプが <u>null</u> となる。<br> <br> <code>Object.getPrototypeOf()</code> でチェーンを辿る例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> class Animal { speak() { console.log("鳴く"); } } class Dog extends Animal {} const dog = new Dog(); // プロトタイプチェーンを確認する console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // true console.log(Object.getPrototypeOf(Object.prototype)); // null (チェーンの終端) </syntaxhighlight> <br> ==== プロトタイプチェーンの仕組み ==== オブジェクトのプロパティやメソッドにアクセスすると、JavaScriptエンジンは以下の順序で探索を行う。<br> <br> # オブジェクト自身のプロパティを探索する。 # 見つからない場合、<code>[[Prototype]]</code> (プロトタイプ) のプロパティを探索する。 # 見つからない場合、プロトタイプのプロトタイプを探索する。 # <code>null</code> に到達しても見つからない場合、<u>undefined</u> を返す。 <br> <u>子のプロパティが親の同名プロパティを隠す現象をプロパティのシャドウイングと呼ぶ。</u><br> <br> 多段継承でのプロパティ探索の実演を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> class A { hello() { return "Aのhello"; } common() { return "Aのcommon"; } } class B extends A { hello() { return "Bのhello"; } // Aのhelloをシャドウイングする } class C extends B { // helloもcommonも定義しない } const c = new C(); // helloはCにない → Bにある → Bのhelloを使用する console.log(c.hello()); // "Bのhello" // commonはCにない → Bにない → Aにある → Aのcommonを使用する console.log(c.common()); // "Aのcommon" // unknownは、CにもBにもAにもObject.prototypeにもない console.log(c.unknown); // undefined </syntaxhighlight> <br> ==== classとプロトタイプの関係 ==== <code>class</code> 構文はシンタックスシュガーであり、内部的にはプロトタイプベースの継承を使用している。<br> <br> <code>class</code> 構文とプロトタイプベースの等価なコードを以下に示す。<br> <br> <syntaxhighlight lang="javascript"> // class 構文 class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + " が鳴く"); } } class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } } // 上記と等価なプロトタイプベースのコード function AnimalFn(name) { this.name = name; } AnimalFn.prototype.speak = function() { console.log(this.name + " が鳴く"); }; function DogFn(name, breed) { AnimalFn.call(this, name); // super(name) に相当する this.breed = breed; } // プロトタイプチェーンを設定する (extendsに相当する) Object.setPrototypeOf(DogFn.prototype, AnimalFn.prototype); </syntaxhighlight> <br> 下表に、<code>class</code> 構文とプロトタイプベースの対応関係を示す。<br> <br> <center> {| class="wikitable" |+ class構文とプロトタイプの対応表 ! class構文 !! プロトタイプベースの等価コード !! 説明 |- | <u>class Animal {}</u> || <u>function Animal() {}</u> || クラス宣言はコンストラクタ関数に対応する |- | <u>constructor(name) { this.name = name; }</u> || <u>function Animal(name) { this.name = name; }</u> || コンストラクタ本体はそのまま関数本体になる |- | <u>speak() { ... }</u> || <u>Animal.prototype.speak = function() { ... };</u> || インスタンスメソッドはprototypeに追加される |- | <u>static create() { ... }</u> || <u>Animal.create = function() { ... };</u> || 静的メソッドはコンストラクタ関数自体のプロパティになる |- | <u>class Dog extends Animal</u> || <u>Object.setPrototypeOf(Dog.prototype, Animal.prototype);</u> || extendsはプロトタイプチェーンを設定する |- | <u>super(name)</u> || <u>Animal.call(this, name);</u> || super()は親コンストラクタをthisに対して呼び出す |- | <u>super.speak()</u> || <u>Animal.prototype.speak.call(this);</u> || super.method()は親のprototypeのメソッドを呼び出す |} </center> <br><br> == 型チェック == JavaScriptでオブジェクトの型を検査するには、<code>instanceof</code> と <code>typeof</code> の2つの演算子を使い分ける。<br> それぞれ用途が異なるため、適切に選択することが重要である。<br> <br> ==== instanceof 演算子 ==== <code>instanceof</code> 演算子は、オブジェクトがあるコンストラクタ (クラス) のインスタンスであるかどうかを、プロトタイプチェーンを辿って検査する。<br> 子クラスのインスタンスは親クラスの <code>instanceof</code> でも <code>true</code> を返すため、継承チェーン全体を検査できる。<br> <br> <code>instanceof</code> の挙動を示す例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> class Animal {} class Dog extends Animal {} class Cat extends Animal {} const dog = new Dog(); console.log(dog instanceof Dog); // true (Dogのインスタンスである) console.log(dog instanceof Animal); // true (プロトタイプチェーンを辿ってAnimalも見つかる) console.log(dog instanceof Cat); // false (Catのインスタンスではない) console.log(dog instanceof Object); // true (全てのオブジェクトはObjectのインスタンスである) </syntaxhighlight> <br> ==== typeof との違い ==== <code>typeof</code> はプリミティブ型の判定に使用し、<code>instanceof</code> はオブジェクトの型 (コンストラクタ) の判定に使用する。<br> <u>typeof null === "object"</u> となる点は言語仕様上の既知の問題 (バグ) であり、nullチェックには <code>=== null</code> を使用する必要がある。<br> <br> 下表に、代表的な値に対する <code>typeof</code> と <code>instanceof Object</code> の結果を示す。<br> <br> <center> {| class="wikitable" |+ typeof vs instanceof 比較表 ! 値 !! typeof の結果 !! instanceof Object |- | <code>null</code> || <code>"object"</code> (既知の問題) || false |- | <code>undefined</code> || <code>"undefined"</code> || false |- | <code>"文字列"</code> || <code>"string"</code> || false |- | <code>42</code> || <code>"number"</code> || false |- | <code>true</code> || <code>"boolean"</code> || false |- | <code>{}</code> || <code>"object"</code> || true |- | <code>[]</code> || <code>"object"</code> || true |- | <code>function() {}</code> || <code>"function"</code> || true |- | <code>new Date()</code> || <code>"object"</code> || true |- | <code>new Dog()</code> || <code>"object"</code> || true |} </center> <br> 型チェックの使い分けの例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> function processValue(value) { // null チェック: typeof では "object" になるため === null を使用する if (value === null) { console.log("null です"); return; } // プリミティブ型の判定には typeof を使用する if (typeof value === "string") { console.log("文字列: " + value.toUpperCase()); return; } if (typeof value === "number") { console.log("数値: " + value.toFixed(2)); return; } // オブジェクトの型の判定には instanceof を使用する if (value instanceof Date) { console.log("日付: " + value.toISOString()); return; } if (Array.isArray(value)) { // 配列チェックには Array.isArray() が確実である console.log("配列 (要素数: " + value.length + ")"); return; } console.log("オブジェクト"); } processValue(null); // "null です" processValue("hello"); // "文字列: HELLO" processValue(3.14159); // "数値: 3.14" processValue(new Date()); // "日付: 2026-02-19T..." processValue([1, 2, 3]); // "配列 (要素数: 3)" processValue({ x: 1 }); // "オブジェクト" </syntaxhighlight> <br><br> == ReactのErrorBoundaryとクラス継承 == <u>Reactでは関数コンポーネントとHooksが主流となっているが、ErrorBoundaryは現在もクラスコンポーネントで実装しなければならない機能の代表例である。</u><br> <br> これは、エラーキャッチに必要なライフサイクルメソッドがHooksで代替できないためであり、クラス継承が実際の開発において今なお不可欠であることを示している。<br> <br> ==== ErrorBoundaryとは ==== ErrorBoundaryは、子コンポーネントツリーのレンダリングエラーをキャッチしてアプリケーション全体のクラッシュを防ぎ、フォールバックUIを表示するコンポーネントである。<br> <br> * キャッチできるエラー *: レンダリング中に発生したエラー *: ライフサイクルメソッド内で発生したエラー *: コンストラクタ内で発生したエラー *: <br> * キャッチできないエラー *: イベントハンドラ内で発生したエラー (try / catchで対処する) *: 非同期コード内で発生したエラー (Promiseのrejection等) <br> ==== クラス継承が必要な理由 ==== ErrorBoundaryには以下の2つのライフサイクルメソッドが必要であり、いずれもクラスコンポーネント専用である。<br> <br> <center> {| class="wikitable" |+ 各メソッドの動作 ! メソッド !! 動作 |- | <code>static getDerivedStateFromError(error)</code> || エラー発生時に状態を更新してフォールバックUIをレンダリングするために使用する。<br>静的メソッドであり、<code>static</code> キーワードを付けて定義する。 |- | <code>componentDidCatch(error, errorInfo)</code> || エラーの詳細情報をログに記録するために使用する。<br>エラーのスタックトレース等の情報 (<code>errorInfo.componentStack</code>) を参照できる。 |} </center> <br> <u>React Hooksには、<code>getDerivedStateFromError</code> と <code>componentDidCatch</code> に相当する機能が存在しない。</u><br> <u>そのため、ErrorBoundaryは現在もクラスコンポーネントで実装する必要がある。</u><br> <br> ==== ErrorBoundaryの実装例 ==== * ErrorBoundaryの実装例 *: <syntaxhighlight lang="javascript"> import React from "react"; class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError : false, error : null, }; } // エラー発生時にstateを更新してフォールバックUIをレンダリングする static getDerivedStateFromError(error) { return { hasError : true, error : error, }; } // エラーの詳細情報をログに記録する componentDidCatch(error, errorInfo) { console.error("ErrorBoundary がエラーをキャッチしました:", error); console.error("コンポーネントスタック:", errorInfo.componentStack); // 外部エラートラッキングサービスへの送信例 // reportError(error, errorInfo); } // エラー状態をリセットしてコンテンツを再表示する handleReset = () => { this.setState({ hasError: false, error: null }); }; render() { if (this.state.hasError) { return ( <div> <h2>予期しないエラーが発生しました</h2> <p>{this.state.error?.message}</p> <button onClick={this.handleReset}>再試行</button> </div> ); } // エラーがない場合は子コンポーネントをそのまま表示する return this.props.children; } } export default ErrorBoundary; </syntaxhighlight> *: <br> * ErrorBoundaryの使用例 *: <syntaxhighlight lang="javascript"> import React from "react"; import ErrorBoundary from "./ErrorBoundary"; import UserProfile from "./UserProfile"; import DataTable from "./DataTable"; function App() { return ( <div> {/* アプリケーション全体をラップする */} <ErrorBoundary> <UserProfile userId={1} /> </ErrorBoundary> {/* 特定のセクションだけをラップすることもできる */} <ErrorBoundary> <DataTable dataSource="api/records" /> </ErrorBoundary> </div> ); } export default App; </syntaxhighlight> <br><br> == 関連情報 == * [[JavaScriptの基礎 - クラス]] *: クラスの宣言、コンストラクタ、メソッド、フィールド、静的メンバー * [[JavaScriptの基礎 - thisキーワード]] *: thisの動作、bind / call / apply、クラスにおけるthis * [[JavaScriptの基礎 - オブジェクトリテラル]] *: オブジェクトの作成と操作、プロパティアクセス、メソッド * [[JavaScriptの基礎 - アロー関数]] *: アロー関数の構文、レキシカルthis <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Web]]
JavaScriptの基礎 - 継承とプロトタイプ
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse