問題一覧
1
以下の操作はクラス自体にプロパティを割り当てている。 これをクラス独自のやり方で実装するとどうなるか? class User {} User.method = function () { alert(this === User); }; User.method(); // true
クラス全体にメソッドを割り当てることもできます。このようなメソッドは static(静的) と呼ばれます。 クラス宣言の中では、次のように static キーワードを付けます。 class User { static staticMethod() { alert(this === User); } } User.staticMethod(); // true
2
staticメソッドはどういう場合に使用されるか? 例を3つ挙げよ
通常 static メソッドは、クラスには属するが、特定のオブジェクトには属さない関数を実装するのに使用されます。 1. 比較用のメソッド 例えば、Article オブジェクトがあり、それらを比較するための関数が必要とします。 自然な解決策は、静的メソッド Article.compare を追加することです。: class Article { constructor(title, date) { this.title = title; this.date = date; } static compare(articleA, articleB) { return articleA.date - articleB.date; } } // usage let articles = [ new Article("Mind", new Date(2019, 1, 1)), new Article("Body", new Date(2019, 0, 1)), new Article("JavaScript", new Date(2019, 11, 1)) ]; articles.sort(Article.compare); alert( articles[0].title ); // Body ここでは、Article.compare は記事を比較する手段として、記事の “上位” にいます。記事のメソッドと言うよりはクラス全体のメソッドです。 2 ファクトリーメソッド 別の例は、いわゆる “ファクトリー” メソッドです。 記事の作成には複数の方法が必要です: 与えられたパラメータ(title, date など)による作成 今日の日付の空の記事の作成 … 最初の方法はコンストラクタで実装することができます。そして、2 つ目の方法としてはクラスの静的メソッドを作ることができます。 ここでの Article.createTodays() を見てください: class Article { constructor(title, date) { this.title = title; this.date = date; } static createTodays() { // 思い出してください, this = Article return new this("Today's digest", new Date()); } } let article = Article.createTodays(); alert( article.title ); // Todays digest これで今日のダイジェストを作成する必要があるたびに、Article.createTodays() を呼べます。 3 データベース関連クラスの操作メソッド 静的メソッドは、次のように、データベース関連のクラスでデータベースの検索/保存/削除のためにも使用されます。: // Article は記事を管理するための特別なクラスと仮定します // 記事を削除するための static メソッド: Article.remove({ id: 12345 });
3
以下と同じ操作をクラス独自のやり方で実装せよ class Article { } Article.publisher = "Ilya Kantor";
静的プロパティも可能で、通常のクラスプロパティと同じように見えますが、先頭に static が付きます。 class Article { static publisher = "Ilya Kantor"; } alert(Article.publisher); // Ilya Kantor
4
静的プロパティやメソッドを定義したクラスを継承した場合どうなるか?
プロトタイプチェーンが形成されるので、継承先のクラスでも静的プロパティやメソッドを継承し、使えるようになる。 class Animal { static planet = "Earth"; constructor(name, speed) { this.speed = speed; this.name = name; } run(speed = 0) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } static compare(animalA, animalB) { return animalA.speed - animalB.speed; } } // Inherit from Animal class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } } let rabbits = [ new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5) ]; rabbits.sort(Rabbit.compare); rabbits[0].run(); // Black Rabbit runs with speed 5. alert(Rabbit.planet); // Earth
5
継承すると継承元の静的プロパティが利用できるのはなぜか? 以下を例にextendの機能も交えて、クラス側とオブジェクト側の観点で解説せよ class Animal { static planet = "Earth"; constructor(name, speed) { this.speed = speed; this.name = name; } run(speed = 0) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } static compare(animalA, animalB) { return animalA.speed - animalB.speed; } } // Inherit from Animal class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } } let rabbits = [ new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5) ]; rabbits.sort(Rabbit.compare); rabbits[0].run(); // Black Rabbit runs with speed 5. alert(Rabbit.planet); // Earth
クラス側の解説 extendすることで、 Rabbitクラス(関数ともいう)の[[prototype]]がAnimalクラス(関数)になる。 extendsはコンストラクタ関数間のプロトタイプチェーンの掲載 (つまるところ関数オブジェクトのプロパティの継承) とプロトタイプ間のチェーン形成 インスタンス→Rabbit.prototype → Animal.prototype を行う class Animal {} class Rabbit extends Animal {} // 静的 alert(Rabbit.__proto__ === Animal); // true // 通常のメソッド alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
6
オブジェクト指向プログラミングでは、プロパティとメソッドは 2 つのグループに分けられます そのグループとはなにか?
内部インタフェース: クラスの他のメソッドからアクセス可能だが、外側からはアクセスできないメソッドやプロパティ。 外部インタフェース: 外部のクラスからもアクセス可能なメソッドやプロパティ。 コーヒーメーカーで例えるなら、内部に隠されているもの: ボイラーチューブや発熱体など、は内部インタフェースです。 内部インタフェースはオブジェクトが機能するために使われ、その構成要素はお互いに使用されます。例えば、ボイラーチューブは発熱体に取り付けられます。 しかし、コーヒーメーカーの外側からは、誰もそこに届かないよう保護カバーで閉ざされています。 内部の詳細は隠されており、アクセスできません。私たちは、外部インタフェースを介してのみその機能を利用できます。
7
オブジェクトを使用するにあたって注目すべき要素はなにか?
外部インターフェース 内部でどのように動いているか完全に分からないかもしれませんが、問題ありません
8
外部インターフェースや内部インタフェースを実現するために、JavaScriptはどのような種類のプロパティが存在するか? 3種類あげよ
パブリック(public): どこからでもアクセス可能です。これらは外部インタフェースになります。 今まで、私たちはパブリックなプロパティとメソッドのみを使用していました。 プライベート(private): クラス内部からのみアクセスできます。これらは内部インタフェース用です。 プロテクト(protected)” : これは、クラス及び、そのクラスを継承したサブクラスの内部からのみアクセス可能であることを意味します。これも内部インタフェースには役立ちます。通常は、継承しているクラスを適切に拡張できるよう、それらにアクセスさせたいため、ある意味ではプライベートよりも広く知られています。 ※ちなみにJavaScriptでは実装されてないので、言語レベルで制御はされず、運用としての実装になる。。
9
protectedプロパティはどのようにして定義するか?
_をつける。 class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { if (value < 0) { value = 0; } this._waterAmount = value; } get waterAmount() { return this._waterAmount; } constructor(power) { this._power = power; } } // コーヒーメーカーを生成 let coffeeMachine = new CoffeeMachine(100); // 水を追加 coffeeMachine.waterAmount = -10; // _waterAmount は -10 ではなく、 0 になります
10
クラスで読み取り専用のプロパティを作るにはどうするか?
セッターはなしで、ゲッターだけ利用するようにすれば読み取り専用になる。 例:powerプロパティが読み取り専用 class CoffeeMachine { // ... constructor(power) { this._power = power; } get power() { return this._power; } } // コーヒーメーカーを作成 let coffeeMachine = new CoffeeMachine(100); alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W coffeeMachine.power = 25; // Error (setter はないので) プロパティフラグとかでも同じことできるけどこっちのほうがらく
11
以下のコードで、????の部分は何が出力されるか? class CoffeeMachine { // ... constructor(power) { this._power = power; } get power() { return this._power; } } // コーヒーメーカーを作成 let coffeeMachine = new CoffeeMachine(100); alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W alert(coffeeMachine._power); //????
100が出力される。 protectedは言語レベルでサポートされてない。 powerプロパティで操作してね!お願い!ってことやね。
12
プライベートなプロパティ はどうやって実装するか?
プライベートは # から始める必要があります。それらはクラス内部からのみアクセス可能です。 例えば、ここではプライベートな #waterLimit プロパティを追加し、水量をチェックするロジックを別のメソッドに抜き出しています: また、メソッドもプライベートにしている。 class CoffeeMachine { #waterLimit = 200; #fixWaterAmount(value) { if (value < 0) return 0; if (value > this.#waterLimit) return this.#waterLimit; } setWaterAmount(value) { this.#waterLimit = this.#fixWaterAmount(value); } } let coffeeMachine = new CoffeeMachine(); // can't access privates from outside of the class coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error ちなみにクロージャとかweakmapを使うと プライベートなプロパティを実装できるっぽいけど上記の方が楽!!
13
パブリックなプロパティの実装方法は? またプライベートなプロパティとパブリックなプロパティは名前衝突するか?
なにもしなければパブリック。 プライベートフィールドはパブリックなものと衝突しません。 プライベートな #waterAmount とパブリックな waterAmount フィールド両方を同時にもつことができます。 例えば、#waterAmount のアクセサとなる waterAmount を作りましょう。: class CoffeeMachine { #waterAmount = 0; get waterAmount() { return this.#waterAmount; } set waterAmount(value) { if (value < 0) value = 0; this.#waterAmount = value; } } let machine = new CoffeeMachine(); machine.waterAmount = 100; alert(machine.#waterAmount); // Error
14
CoffeeMachineクラスで#waterAmountプロパティを定義し継承した。 以下のアラートはどうなるか? class Nanka extends CoffeeMachine{ method() { alert( this.#waterAmount ); //? } }
プライベートは継承先でも使えない プロテクテドならいける。
15
privateとprotectedどっちがいいの?
protected privateは新しめの機能なので、ブラウザによってはサポートされてなかったりするし、 継承先で使えないという制限は厳しすぎなので、protectedがちょうどいい。
16
privateを利用する上での制限事項をあげよ
this['#name'] は期待通り動作しません。 試したらundefined がかえってきた。 this.#nameはいける。 Private フィールドは特別です。 これは private であることを維持するための、構文上の制限になります。 外側から使われないから内部で気をつけるだけだけど 文字列でアクセスできないのはどうなんだろな Object.entries(obj)とかでプロパティ情報とってきて以下のようにクローンしようとしてもプライベートなプロパティはコピーできないかも? obj[key] = value; いやそもそも外部からアクセスできないか
17
コーヒーマシンの例のように、外部インタフェースと内部インタフェースを切り離すことを、なんというか?
カプセル化
18
カプセル化の利点とは?
1 外部から変更されずらくする。 外部からの変更を意図していないものが勝手に変更された場合、結果は予測不可能です。 2 利用者へ通知せず、内部のコード変更が容易 もし内部インタフェースを厳密に区切ると、クラスの開発者は利用者へ通知しなくても内部のプロパティとメソッドを自由に変更することができます。 3 ドキュメント化しやすい。 実装の詳細が隠されていて、シンプルかつ良くドキュメント化された外部インタフェースが利用可能であることは常に便利です。
19
組み込みであるArrayクラスを継承して、別のクラスを作成した。 以下の例では、powerArrayオブジェクトのarrに対して継承元のメソッドを利用してフィルターを行っている。 戻り値のインスタンスのクラスはpowerArrayか? (戻り値のプロパティにisEmptyは含まれているか?) また、????の部分はどうなるか? class PowerArray extends Array { isEmpty() { return this.length === 0; } } ↓スーパー定義のコンストラクタが使える。 let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false let filteredArr = arr.filter((item) => item >= 10); alert(filteredArr); // 10, 50 alert(filteredArr.isEmpty()); //????
戻り値のインスタンスのクラスはpowerArrayとなる。 Arrayのメソッドなのに、Arrayのインスタンスがかえってくるわけではない! よって????の部分はエラーにはならず処理が動き、emptyどないので、falseとなる。 isEmptyメソッドはクラスメソッド(プロトタイプメソッド)なので、 つまり、返却されたオブジェクトの[[prototype]]がPowerArray.prototype のまま変わってないということがわかる。 なぜこういう動きとなるのか次問題にて解説
20
前問のように filter や map といった組み込みのメソッドは、継承されたクラス と同じクラスの新しいオブジェクトを返しますのは内部的にどうなっているか?
実行コンテキストとなるオブジェクトのconstructorプロパティを利用して、インスタンスしているから。 また、実行コンテキストのconstructorプロパティとは、実行コンテキストであるオブジェクトのクラス(コンストラクタ関数)のことである。 前問の例では以下の通りとなる。 arr.constructor === PowerArray;
21
組み込み関数のmapやfilterなどで オブジェクトのコンストラクタを利用する動作を変更するにはどうするか? 例えば 継承されたオブジェクトではなく、Array型を返却するようにしたいときどうするか? また、Map や Set などの他のコレクションも同様に動作するか?
特別な静的な getter Symbol.species を追加することができます。 getter Symbol.species では、利用するコンストラクタ関数を指定します。 これがあると、filterやmap関数は↑のゲッター関数で指定されたコンストラクタ関数を利用して値を返すようになる! なので、Array型を返したい場合は Arrayクラス=コンストラクタ関数なので、これを指定すればよい。 class PowerArray extends Array { isEmpty() { return this.length === 0; } // 組み込みのメソッドは、これをコンストラクタとして使います!! static get [Symbol.species]() { return Array; } } let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false // filter はコンストラクタとして arr.constructor[Symbol.species] を使って新しい配列を作ります let filteredArr = arr.filter(item => item >= 10); // filteredArr は PowerArray ではなく Array です alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function また、Map や Set などの他のコレクションも同様に動作する。 これらも Symbol.species を使用しています。
22
組み込みクラス同士の継承と 通常のクラス同士の継承との違いをあげよ
通常のクラス同士の継承は、 コンストラクタ関数自体のプロトタイプの継承と コンストラクタ関数.プロトタイプオブジェクトの継承の二つがあった。 だけど組み込み同士の場合 コンストラクタ関数自体のプロトタイプの継承がない。 なので、静的なフィールドは継承されない。 まあ確かにいらないか
23
組み込みクラスを親としたカスタムクラスの継承は静的フィードの継承はある?
それはある。試したら動いたので。 組み込み同士がないだけ。
24
instanceof演算子とはなにか?
左辺のオブジェクトが右辺のクラス(コンストラクタ関数)に属するかチェックする時に利用する演算子。 継承も考慮される。 構文は次の通りです: obj instanceof Class それは obj が Class (または、それを継承しているクラス)に属している場合に true を返します。 class Rabbit {} let rabbit = new Rabbit(); // Rabbit クラスのオブジェクト? alert( rabbit instanceof Rabbit ); // true
25
instanceof演算子の右辺はクラスだが、他にも何が動作するか?
当然コンストラクタ関数もいける。 class の代わり function Rabbit() {} alert( new Rabbit() instanceof Rabbit ); // true また Array のような組み込みクラスでも動作します。: let arr = [1, 2, 3]; alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true
26
obj instanceOf Class は内部的にどのような動作となるか?
左辺のオブジェクトのプロトタイプチェーンのうち、 右辺のClass.prototypeとマッチするかチェックしている。 言い換えると、以下のような比較を行います: obj.__proto__ == Class.prototype obj.__proto__.__proto__ == Class.prototype obj.__proto__.__proto__.__proto__ == Class.prototype ... // いずれかが true の場合、 true が返却されます // そうでない場合、チェーンの末尾に到達すると false を返します
27
instanceof 演算子の内部の振る舞いを変更するには?
右辺のクラス側に、 静的メソッド [Symbol.hasInstance](obj)を追加した場合、比較時、このメソッドが呼ばれるようになる。 そのメソッドの引数は左辺のオブジェクトであり、trueを返せば、プロトタイプチェーンのチェックをせずに戻り値をtrueとすることができる。 例えば、以下では、オブジェクトにとあるプロパティが存在していれば、継承関係なく、trueとみなしてしまっている。 // catEat プロパティをもつものは animal と想定する // instanceOf チェックを設定 class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj)が呼ばれます
28
obj instanceof Classを objA.isPrototypeOf(objB)を利用して書き換えるとどうなる?
objA.isPrototypeOf(objB)はobjAがobjBのプロトタイプチェーンに含まれるかチェックなので、 いいかえると Class.prototype.isPrototypeOf(obj) になる。 オブジェクトでもいけるけど、 プロトタイプ側が利用するメソッドだとと覚えておこう! Class.prototypeがobjのプロトタイプチェーンであるか? っていみ
29
オブジェクトのtoStringメソッドについて、以下のコードがある。アラートされる内容はなにか? let obj = {}; alert(obj); // ??? alert(obj.toString()); //????
[object Object]
30
オブジェクトのインスタンスメソッドであるtoStringメソッド = Object.prototype.toString を、Objectのインスタンスではなく、別の型のオブジェクトやプリミティブを実行コンテキストとして実行するとどうなるか?
だいたいtoStringは配列とかだったら要素が出るようにArray.prototype でオーバーライドされてカスタマイズされている。 しかしObject.prototype.toString をArrayのインスタンスを実行コンテキストとして呼び出すとカスタマイズされてない処理が行われる。 数値の場合、それは [object Number] になります。 真偽値の場合、[object Boolean] になります。 null の場合: [object Null] undefined の場合: [object Undefined] 配列の場合: [object Array] …など (カスタマイズ可能). デモを見てみましょう: // 使いやすくするために toString メソッドを変数にコピー let objectToString = Object.prototype.toString; // これの型はなに? let arr = []; // call関数を使用している。 alert( objectToString.call(arr) ); // [object Array]
31
Object.prototype.toStringについて、 利用するオブジェクトで振る舞いを変更するにはどうするか?
特別なオブジェクトプロパティ Symbol.toStringTag を使用してカスタマイズできます。 例: let user = { [Symbol.toStringTag]: 'User' }; //Object.prototype.toStringを短縮してかいてる alert( {}.toString.call(user) ); // [object User] // user.toString()でも変わんなさそう // 環境固有のオブジェクトとクラスのtoStringTag: alert( window[Symbol.toStringTag]); // window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest ほとんどの環境固有のオブジェクトには、このようなプロパティがあります。これはいくつかのブラウザ固有の例です。: alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]