ecmascript:class
                差分
このページの2つのバージョン間の差分を表示します。
| 両方とも前のリビジョン前のリビジョン次のリビジョン | 前のリビジョン | ||
| ecmascript:class [2025/09/09 16:22] – nullpon | ecmascript:class [2025/09/20 13:41] (現在) – nullpon | ||
|---|---|---|---|
| 行 38: | 行 38: | ||
| } | } | ||
| } | } | ||
| - | ``` | ||
| - | |||
| - | クラスの実態は関数。(JSのクラスはオブジェクト生成構文のシンタックスシュガーに過ぎない) | ||
| - | |||
| - | ```javascript | ||
| - | console.log(typeof Test);  | ||
| ``` | ``` | ||
| 行 50: | 行 44: | ||
| ```javascript | ```javascript | ||
| - | class Test2  | + | class Test2 extends Test { | 
| // 拡張メソッド | // 拡張メソッド | ||
| print() { | print() { | ||
| 行 65: | 行 59: | ||
| function Fuga(x) { | function Fuga(x) { | ||
| this.x = x; | this.x = x; | ||
| + | } | ||
| + | |||
| + | Fuga.prototype.inspect = function() { | ||
| + |   console.log(`x: | ||
| } | } | ||
| const fuga = new Fuga(1); | const fuga = new Fuga(1); | ||
| + | fuga.inspect(); | ||
| console.log(typeof Fuga); // => function | console.log(typeof Fuga); // => function | ||
| 行 75: | 行 74: | ||
| ``` | ``` | ||
| - | 以下のコードは実は上記のコードとほぼ同等。class構文で定義したクラスは実は関数なのである。このような関数はコンストラクタ関数と呼ばれる | + | これはclassキーワードを使った以下のクラス定義とほぼ同等。 | 
| ```javascript | ```javascript | ||
| 行 81: | 行 80: | ||
| constructor(x) { | constructor(x) { | ||
| this.x = x; | this.x = x; | ||
| + | } | ||
| + |    | ||
| + | inspect() { | ||
| + |     console.log(`x: | ||
| } | } | ||
| } | } | ||
| const fuga = new Fuga(1); | const fuga = new Fuga(1); | ||
| + | fuga.inspect(); | ||
| console.log(typeof Fuga); // => function | console.log(typeof Fuga); // => function | ||
| 行 91: | 行 95: | ||
| ``` | ``` | ||
| - | ※ ただしfunctionによるコンストラクタ定義は過去のコードとの互換性のために残されているので現在は非推奨でありclassキーワードを使うべき | + | classキーワードで定義したクラスの正体は関数で、newキーワードとともに新しいオブジェクトを生成する関数をコンストラクタ関数と呼ぶ。コンストラクタ関数で生成されたオブジェクトはクラスのインスタンスであるかのように振る舞うが、実際にはクラスとは異なる**prototype chain**という仕組みで動作している。 | 
| + | |||
| + | なお、functionでコンストラクタ関数(クラス)を定義することは可能だが、あえてそうする理由はなくclassキーワードを使うべきである | ||
| - | これは何を意味しているかというとclassと銘打っているが、その実体はクラスではないということだ。クラスのような振る舞いはprototype chainという仕組みで行われている。 | ||
| ## constructorプロパティ | ## constructorプロパティ | ||
| 行 101: | 行 106: | ||
| ```javascript | ```javascript | ||
| class Hoge { | class Hoge { | ||
| - | constructor(name) { | + | constructor(x) { | 
| - | this.name = name; | + | this.x = x; | 
| - | } | + | |
| - | + | ||
| - | getName() { | + | |
| - | return this.name; | + | |
| } | } | ||
| } | } | ||
| 行 122: | 行 123: | ||
| ```javascript | ```javascript | ||
| + | class Hoge { | ||
| + | constructor(x) { | ||
| + | this.x = x; | ||
| + | } | ||
| + |    | ||
| + | getNumber() { | ||
| + | return this.x; | ||
| + | } | ||
| + | } | ||
| + | |||
| console.log(Hoge.prototype); | console.log(Hoge.prototype); | ||
| ``` | ``` | ||
| - | classで定義したメソッドは、このprototypeプロパティが持っている。 | + | classで定義したメソッドは、このprototypeプロパティに所属している。 | 
| ```javascript | ```javascript | ||
| - | console.log(Hoge.prototype, | + | console.log(Hoge.prototype, | 
| - | + | ||
| - | console.log(Hoge.prototype, | + | |
| ``` | ``` | ||
| - | コンストラクタ関数を通して生成されたオブジェクトは`__proto__`というプロパティを持っており、コンストラクタ関数のprototypeにアクセスできる | + | コンストラクタ関数によって生成されたオブジェクトは`__proto__`というプロパティを持っており、コンストラクタ関数のprototypeにアクセスできる。 | 
| - | ``` | + | ```javascript | 
| - | console.log(h.__proto__.getName); // => [function  | + | console.log(h.__proto__.getNumber); // => [function  | 
| ``` | ``` | ||
| `h.__proto__`も`__proto__`を持っている。このようなprototypeの連鎖を**prototype chain**と呼ぶ | `h.__proto__`も`__proto__`を持っている。このようなprototypeの連鎖を**prototype chain**と呼ぶ | ||
| - | ``` | + | ```javascript | 
| console.log(h.__proto__);  | console.log(h.__proto__);  | ||
| console.log(h.__proto__.__proto__);  | console.log(h.__proto__.__proto__);  | ||
| - | ``` | + | ``` | 
| - | この場合は2つまでだが、クラスを継承することでprototype chainは長くなっていく。これはメソッドの呼び出しに関係している | + | `h.getNumber()`というメソッド呼び出しは、`h.__proto__`に委譲することで実現している。そして`new Hoge()`で生成されたオブジェクトはprototypeを共有することで同じメソッドを共有し、Hogeはクラスのように動作する。 | 
| - | x.m()というコードを実行すると | + | ## prototype chainとメソッド呼び出し | 
| - | 1. `x`自身プロパティ一覧からmという名前のプロパティを探し、見つかったらメソッドとして実行する。なければ2へ | + | JSのメソッドは関数が入ったプロパティである。よってプロパティに関数をセットすることでもメソッドを定義できる。 | 
| - | 2. `x,__proto__`のプロパティ一覧からmという名前の・・・、なければ3へ | + | |
| - | 3. `x.__proto__.__proto__`のプロパティ一覧からmという名前の・・・、なければ4へ | + | ```javascript | 
| - | 4. prototype chainの末端まで到達しても見つからない場合はTypeErrorとなる | + | class Hoge { | 
| + | |||
| + | // プロパティの定義 | ||
| + |   name = ''; | ||
| + | |||
| + | // コンストラクタ | ||
| + | constructor(name) { | ||
| + |     this.name = name || ''; | ||
| + | } | ||
| + | |||
| + | // メソッド | ||
| + | getName() { | ||
| + |     return this.name; | ||
| + | } | ||
| + | |||
| + | // プロパティに関数を入れてもメソッドになる(注意:これは実アプリでは避けるべき方法) | ||
| + |    | ||
| + | return this,name?.length || 0; | ||
| + | } | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | ※ 通常のメソッドはprototypeにセットされて複数のHogeオブジェクト間で共有されるが、プロパティにセットしたgetCountはnewするたびに関数が作成されて共有されず各Hogeオブジェクトが個別に持つ。これはリソースの無駄なので実際のアプリ構築時には避けるべきコードである | ||
| + | |||
| + | |||
| + | ```javascript | ||
| + | h = new Hoge(' | ||
| + | |||
| + | h, | ||
| + | h, | ||
| + | h, | ||
| + | ``` | ||
| + | |||
| + | getCountはオブジェクト自身が持っているメソッドなので直接呼び出される。getNameはprototypeが持っているメソッドなのでprototypeに処理が委譲される。 | ||
| + | |||
| + | オブジェクトがメソッドを持っていなければ、prototypeに処理が委譲され、prototypeも持っていなければprototypeのprototypeに処理が委譲される。JSはprototype chainを遡ってメソッドを検索し、見つかったらそのprototypeに処理を委譲する。こうしてクラスのような振る舞いを実現している。 | ||
| + | |||
| + | したがって、getNameやtoStringの呼び出しは次のコードと概念的には同等である。 | ||
| + | |||
| + | ```javascript | ||
| + | h.__proto__.getName.call(h); | ||
| + | h.__proto__.__proto__.toString.call(h); | ||
| + | ``` | ||
| + | |||
| + | プロパティやメソッドの所有者は`Object.hasOwn`で調べることができる | ||
| + | |||
| + | ```javascript | ||
| + | console.log(Object.hasOwn(h, | ||
| + | console.log(Object.hasOwn(h, | ||
| + | console.log(Object.hasOwn(h, | ||
| + | console.log(Object.hasOwn(h, | ||
| + | |||
| + | console.log(Object.hasOwn(h.__proto__, | ||
| + | console.log(Object.hasOwn(h.__proto__, | ||
| + | |||
| + | console.log(Object.hasOwn(h.__proto__.__proto__, | ||
| + | ``` | ||
| + | |||
| + | prototype chainの末端まで探してもメソッドが見つからない場合はTypeErrorが発生する。何も継承していないクラスの場合prototype chainは2つで終わりだが、継承すると継承した回数だけprototype chainは長くなる。toStringやvalueOfなど定義しなくても存在しているメソッドはprototype chain末端が持っているメソッドである。 | ||
| + | |||
| + | ### オーバーライド | ||
| + | |||
| + | オーバーライドはprototype chainのより近い位置に同名のメソッドを定義することで実現される | ||
| + | |||
| + | ```javascript | ||
| + | class Hoge { | ||
| + | toString() { | ||
| + |     return super.toString() + " | ||
| + | } | ||
| + | } | ||
| + | |||
| + | const h = new Hoge(); | ||
| + | |||
| + | // toStringはprototype chainの末端が持っている | ||
| + | console.log(Object.hasOwn(h.__proto__.__proto__, | ||
| + | |||
| + | // しかしオーバーライドしたのでHogeのprototypeも持っている | ||
| + | console.log(Object.hasOwn(h.__proto__, | ||
| + | |||
| + | // 呼び出されるのは、prototype chainのより近い側にあるメソッド | ||
| + | console.log(h.toString());  | ||
| + | ``` | ||
| + | |||
| + | |||
| + | ### prototypeを指定してオブジェクトを生成 | ||
| + | |||
| + | `Object.create`を使うと任意のオブジェクトをprototypeに設定して新しいオブジェクトを生成できる | ||
| + | |||
| + | ```javascript | ||
| + | const p = { | ||
| + |   name: ' | ||
| + | greet() { | ||
| + |     console.log(" | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // pをプロトタイプに設定してオブジェクトを生成 | ||
| + | const h = Object.create(p);  | ||
| + | |||
| + | console.log(h.__proto__ === p); // => true | ||
| + | |||
| + | // prototypeのnameが使われる | ||
| + | h.greet(); // => I am a proto | ||
| + | |||
| + | // hのnameを設定 | ||
| + | h.name = " | ||
| + | |||
| + | // h自身がnameを持っていれば、プロトタイプではなく自身のnameが使われる | ||
| + | h.greet(); // => I am a cat | ||
| + | ``` | ||
| + | |||
| + | - [Object.create() - JavaScript | MDN(developer.mozilla.org)](https:// | ||
| </ | </ | ||
| + | |||
| + | |||
ecmascript/class.1757434975.txt.gz · 最終更新:  by nullpon