内容へ移動
Cat Paw Software
ユーザ用ツール
ログイン
サイト用ツール
検索
ツール
文書の表示
以前のリビジョン
バックリンク
最近の変更
メディアマネージャー
サイトマップ
ログイン
>
最近の変更
メディアマネージャー
サイトマップ
トレース:
•
html5
•
cdn
•
build-tools
ecmascript:class
この文書は読取専用です。文書のソースを閲覧することは可能ですが、変更はできません。もし変更したい場合は管理者に連絡してください。
<markdown> # class ## クラス構文 ```javascript class Test { // 静的フィールド static version = '1.0.0'; // 静的メソッド static info() { // 注意 staticコンテキストでのthis.nameはクラス名を指す(`#name`ではない) return `${this,name} class, version ${this.version}` } // privateフィールド #name = ''; // コンストラクタ constructor(v) { this.#name = v; } // メソッド getMessage() { return 'Hello from ${this.#name}' } // getter get name() { return this.#name; } // setter set name(v) { this.#name = v; } } ``` ## 継承 ```javascript class Test2 extends Test { // 拡張メソッド print() { console.log(this.getMessage()); } } ``` ## 関数とnew ここからはマニア向けのディープな話になる。JSの関数はコンストラクタとして機能し、new演算子によって新しいオブジェクトを生成できる ```javascript function Fuga(x) { this.x = x; } Fuga.prototype.inspect = function() { console.log(`x: ${this.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: ${this.x}`); } } const fuga = new Fuga(1); fuga.inspect(); console.log(typeof Fuga); // => function // ただしclassで定義した関数は直接呼べないように制限がかかっている Fuga(); // TypeError: Class constructor Fuga cannot be invoked without 'new' ``` classキーワードで定義したクラスの正体は関数で、newキーワードとともに新しいオブジェクトを生成する関数をコンストラクタ関数と呼ぶ。コンストラクタ関数で生成されたオブジェクトはクラスのインスタンスであるかのように振る舞うが、実際にはクラスとは異なる**prototype chain**という仕組みで動作している。 なお、functionでコンストラクタ関数(クラス)を定義することは可能だが、あえてそうする理由はなくclassキーワードを使うべきである ## constructorプロパティ 生成されたオブジェクトはconstructorというプロパティを持っている。これは自身を生成したコンストラクタ関数への参照である。 ```javascript class Hoge { constructor(x) { this.x = x; } } const h = new Hoge(1) console.log(h,constructor === Hoge); // => true const h2 = new h.constructor(2); // newすることもできる ``` ## 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,getNumber); // => [function getNumber] ``` コンストラクタ関数によって生成されたオブジェクトは`__proto__`というプロパティを持っており、コンストラクタ関数のprototypeにアクセスできる。 ```javascript console.log(h.__proto__.getNumber); // => [function getNumber] ``` `h.__proto__`も`__proto__`を持っている。このようなprototypeの連鎖を**prototype chain**と呼ぶ ```javascript console.log(h.__proto__); // => {} console.log(h.__proto__.__proto__); // => [Object: null prototype] {} ``` `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,name?.length || 0; } } ``` ※ 通常のメソッドはprototypeにセットされて複数のHogeオブジェクト間で共有されるが、プロパティにセットしたgetCountはnewするたびに関数が作成されて共有されず各Hogeオブジェクトが個別に持つ。これはリソースの無駄なので実際のアプリ構築時には避けるべきコードである ```javascript h = new Hoge('example'); h,getCount(); // h自身がgetCountを持っている h,getName(); // h.__proto__がgetNameを持っている h,toString(); // h.__proto__.__proto__がtoStringを持っている ``` 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, "name")); // true console.log(Object.hasOwn(h, "getCount")); // true console.log(Object.hasOwn(h, "getName")); // false console.log(Object.hasOwn(h, "toString")); // false console.log(Object.hasOwn(h.__proto__, "getName")); // true console.log(Object.hasOwn(h.__proto__, "toString")); // false console.log(Object.hasOwn(h.__proto__.__proto__, "toString")); // true ``` 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__, "toString"); // => true // しかしオーバーライドしたのでHogeのprototypeも持っている console.log(Object.hasOwn(h.__proto__, "toString"); // => true // 呼び出されるのは、prototype chainのより近い側にあるメソッド console.log(h.toString()); // => [object Object]にゃん ``` ### prototypeを指定してオブジェクトを生成 `Object.create`を使うと任意のオブジェクトをprototypeに設定して新しいオブジェクトを生成できる ```javascript const p = { name: 'proto', greet() { console.log("I am a " + this.name) } } // pをプロトタイプに設定してオブジェクトを生成 const h = Object.create(p); console.log(h.__proto__ === p); // => true // prototypeのnameが使われる h.greet(); // => I am a proto // hのnameを設定 h.name = "cat"; // h自身がnameを持っていれば、プロトタイプではなく自身のnameが使われる h.greet(); // => I am a cat ``` - [Object.create() - JavaScript | MDN(developer.mozilla.org)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/create) </markdown>
ecmascript/class.txt
· 最終更新:
2025/09/20 13:41
by
nullpon
ページ用ツール
文書の表示
以前のリビジョン
バックリンク
文書の先頭へ