ecmascript:class
                差分
このページの2つのバージョン間の差分を表示します。
| 両方とも前のリビジョン前のリビジョン次のリビジョン | 前のリビジョン | ||
| ecmascript:class [2025/09/09 06:02] – nullpon | ecmascript:class [2025/09/20 13:41] (現在) – nullpon | ||
|---|---|---|---|
| 行 1: | 行 1: | ||
| < | < | ||
| # class | # class | ||
| + | |||
| + | ## クラス構文 | ||
| ```javascript | ```javascript | ||
| 行 36: | 行 38: | ||
| } | } | ||
| } | } | ||
| - | ``` | ||
| - | |||
| - | クラスの実態は関数。(JSのクラスはオブジェクト生成構文のシンタックスシュガーに過ぎない) | ||
| - | |||
| - | ```javascript | ||
| - | console.log(typeof Test);  | ||
| ``` | ``` | ||
| 行 48: | 行 44: | ||
| ```javascript | ```javascript | ||
| - | class Test2  | + | class Test2 extends Test { | 
| // 拡張メソッド | // 拡張メソッド | ||
| print() { | print() { | ||
| 行 55: | 行 51: | ||
| } | } | ||
| ``` | ``` | ||
| + | |||
| + | ## 関数とnew | ||
| + | |||
| + | ここからはマニア向けのディープな話になる。JSの関数はコンストラクタとして機能し、new演算子によって新しいオブジェクトを生成できる | ||
| + | |||
| + | ```javascript | ||
| + | function Fuga(x) { | ||
| + | this.x = x; | ||
| + | } | ||
| + | |||
| + | Fuga.prototype.inspect = function() { | ||
| + |   console.log(`x: | ||
| + | } | ||
| + | |||
| + | const fuga = new Fuga(1); | ||
| + | fuga.inspect(); | ||
| + | |||
| + | console.log(typeof Fuga); // => function | ||
| + | |||
| + | // 普通の関数としても呼べる | ||
| + | Fuga();   | ||
| + | ``` | ||
| + | |||
| + | これはclassキーワードを使った以下のクラス定義とほぼ同等。 | ||
| + | |||
| + | ```javascript | ||
| + | class Fuga { | ||
| + | constructor(x) { | ||
| + | this.x = x; | ||
| + | } | ||
| + |    | ||
| + | inspect() { | ||
| + |     console.log(`x: | ||
| + | } | ||
| + | } | ||
| + | const fuga = new Fuga(1); | ||
| + | fuga.inspect(); | ||
| + | |||
| + | console.log(typeof Fuga); // => function | ||
| + | |||
| + | // ただしclassで定義した関数は直接呼べないように制限がかかっている | ||
| + | Fuga();  | ||
| + | ``` | ||
| + | |||
| + | classキーワードで定義したクラスの正体は関数で、newキーワードとともに新しいオブジェクトを生成する関数をコンストラクタ関数と呼ぶ。コンストラクタ関数で生成されたオブジェクトはクラスのインスタンスであるかのように振る舞うが、実際にはクラスとは異なる**prototype chain**という仕組みで動作している。 | ||
| + | |||
| + | なお、functionでコンストラクタ関数(クラス)を定義することは可能だが、あえてそうする理由はなくclassキーワードを使うべきである | ||
| + | |||
| + | |||
| + | ## constructorプロパティ | ||
| + | |||
| + | 生成されたオブジェクトはconstructorというプロパティを持っている。これは自身を生成したコンストラクタ関数への参照である。 | ||
| + | |||
| + | ```javascript | ||
| + | class Hoge { | ||
| + | constructor(x) { | ||
| + | this.x = x; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | const h = new Hoge(1) | ||
| + | |||
| + | console.log(h, | ||
| + | |||
| + | const h2 = new h.constructor(2);  | ||
| + | ``` | ||
| + | |||
| + | ## prototypeプロパティ | ||
| + | |||
| + | コンストラクタ関数はprototypeというプロパティを持っている | ||
| + | |||
| + | ```javascript | ||
| + | class Hoge { | ||
| + | constructor(x) { | ||
| + | this.x = x; | ||
| + | } | ||
| + |    | ||
| + | getNumber() { | ||
| + | return this.x; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | console.log(Hoge.prototype); | ||
| + | ``` | ||
| + | |||
| + | classで定義したメソッドは、このprototypeプロパティに所属している。 | ||
| + | |||
| + | ```javascript | ||
| + | console.log(Hoge.prototype, | ||
| + | ``` | ||
| + | |||
| + | コンストラクタ関数によって生成されたオブジェクトは`__proto__`というプロパティを持っており、コンストラクタ関数のprototypeにアクセスできる。 | ||
| + | |||
| + | ```javascript | ||
| + | console.log(h.__proto__.getNumber); | ||
| + | ``` | ||
| + | |||
| + | `h.__proto__`も`__proto__`を持っている。このようなprototypeの連鎖を**prototype chain**と呼ぶ | ||
| + | |||
| + | ```javascript | ||
| + | console.log(h.__proto__);  | ||
| + | console.log(h.__proto__.__proto__);  | ||
| + | ``` | ||
| + | |||
| + | `h.getNumber()`というメソッド呼び出しは、`h.__proto__`に委譲することで実現している。そして`new Hoge()`で生成されたオブジェクトはprototypeを共有することで同じメソッドを共有し、Hogeはクラスのように動作する。 | ||
| + | |||
| + | ## prototype chainとメソッド呼び出し | ||
| + | |||
| + | JSのメソッドは関数が入ったプロパティである。よってプロパティに関数をセットすることでもメソッドを定義できる。 | ||
| + | |||
| + | ```javascript | ||
| + | class Hoge { | ||
| + | |||
| + | // プロパティの定義 | ||
| + |   name = '';  | ||
| + |    | ||
| + | // コンストラクタ | ||
| + | constructor(name) { | ||
| + |     this.name = name || ''; | ||
| + | } | ||
| + |    | ||
| + | // メソッド | ||
| + | getName() { | ||
| + | return this.name; | ||
| + | } | ||
| + |    | ||
| + | // プロパティに関数を入れてもメソッドになる(注意:これは実アプリでは避けるべき方法) | ||
| + | getCount = function() { | ||
| + |     return this, | ||
| + | } | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | ※ 通常のメソッドは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.1757397754.txt.gz · 最終更新:  by nullpon