どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のおさらい
前回は、「インスタンスの状態は、インスタンスオブジェクトにだけ保存する」「thisの暗黙的な結合を理解しよう」を2本まとめてお届けしました。
今回は、「スーパークラスのコンストラクタは、サブクラスのコンストラクタから呼び出す」「スーパークラスのプロパティ名は、決して再利用しない」 を2本まとめてお届けします。
テキスト第4章「オブジェクトとプロトタイプ」の項目38、項目39に対応しています。
今日のアジェンダ
- 完成したソースコード
- インクリメント演算子の前置と後置
- スーパークラスのプロパティ名は、決して再利用しない
完成したソースコード
書籍で紹介されていたクラスを、クラス構文に読み替えたものを置いておきます。
これまで、明示的なthis(callメソッドやObject.create()メソッド)は、クラス構文で解決してきました。
なので、今回も、その方向で読み解いていきます。
クラス構文の継承(extends、super())を、ぜひ復習しましょう。
加えて、問題の本質ではありませんので、ブラウザ上で動作するメソッドは、コンソールログ出力するなどして、読み替えてみました。
function myFunction05_38_01() {
/** クラスScene */
class Scene {
constructor(context, width, height, images) {
this.context = context;
this.width = width;
this.height = height;
this.images = images;
this.actors = [];
}
register(actor) {
this.actors.push(actor);
}
unregister(actor) {
const i = this.actors.indexOf(actor);
if (i >= 0) this.actors.splice(i, 1);
}
draw() {
// this.context.clearRect(0, 0, this.width, this.height);
for (const a of this.actors) a.draw();
}
}
/** クラスActor */
class Actor {
constructor(scene, x, y) {
this.scene = scene;
this.x = x;
this.y = y;
scene.register(this);
}
moveTo(x, y) {
this.x = x;
this.y = y;
this.scene.draw();
}
exit() {
this.scene.unregister(this);
this.scene.draw();
}
draw() {
const image = this.scene.images[this.type];
console.log(`背景: ${image}とx: ${this.x}、y: ${this.y}にこのアクターをdrawしました`);
//this.scene.context.drawImage(image, this.x, this.y);
}
width() {
return this.scene.images[this.type].width;
}
height() {
return this.scene.images[this.type].height;
}
}
/**
* クラスSpaceShip
* @extends Actor
*/
class SpaceShip extends Actor {
constructor(scene, x, y) {
super(scene, x, y);
this.points = 0;
this.type = 'spaceShip';
}
scorePoint() {
this.point++;
}
left() {
this.moveTo(Math.max(this.x - 10, 0), this.y);
}
right() {
const maxWidth = this.width - this.width();
this.moveTo(Math.min(this.x + 10, maxWidth), this.y);
}
}
const scene = new Scene('context', 1920, 1000, { spaceShip: 'image1' });
const spaceShip = new SpaceShip(scene, 200, 100);
scene.draw(); //背景: image1とx: 200、y: 100にこのアクターをdrawしました
spaceShip.moveTo(10, 20); //背景: image1とx: 10、y: 20にこのアクターをdrawしました
}
インクリメント演算子の前置と後置
すこしばかり、初級講座の復習です。
インクリメント演算子には、前置と後置があります。
後置はよく使う記法で、変数の評価を先に行い、1を加えます。
一方、前置は、先に1を加えてから、評価を行います。
function myFunction5_39_00() {
let a, b;
//後置の場合、b=aをまず評価し、aに1を加える。
a = 8;
b;
b = a++;
console.log(a, b); //9,8
a = 8;
b;
b = a;
a = a + 1;
console.log(a, b); //9,8
//前置の場合、対象の変数を1だけ増加させて、b=aを評価する。
a = 8;
b;
b = ++a;
console.log(a, b); //9,9
a = 8;
b;
a = a + 1;
b = a;
console.log(a, b); //9,9
}
スーパークラスのプロパティ名は、決して再利用しない
ActorクラスのインスタンスにIDを付与する
それでは、インスタンスにIDを付与する機能を実装してみましょう。
まず、Actorクラスにプロパティidを付与します。
プロパティidには、Actor.nextIDに前置でインクリメントした値を代入します。
/** クラスActor */
class Actor {
constructor(scene, x, y) {
this.scene = scene;
this.x = x;
this.y = y;
this.id = ++Actor.nextID;
scene.register(this);
}
}
グローバル領域では、ActorオブジェクトにnextIDプロパティを定義し、初期化しています。
Actor.nextID = 0;
インスタンスを生成するごとに、IDが付与されます。
const scene = new Scene('context', 1920, 1000, { spaceShip: 'image1' });
const spaceShip1 = new SpaceShip(scene, 200, 100);
console.log(spaceShip1.id); //1
const spaceShip2 = new SpaceShip(scene, 200, 100);
console.log(spaceShip2.id); //2
サブクラスのインスタンスにIDを付与する
さらに、サブクラスのインスタンスにもIDを付与してみましょう。
今回は、Actorクラスのサブクラスとして、Alienクラスを作成します。
/**
* クラスAlien
* @extends Actor
*/
class Alien extends Actor {
constructor(scene, x, y, direction, speed, strength) {
super(scene, x, y);
this.direction = direction;
this.speed = speed;
this.strength = strength;
this.damage = 0;
this.id = ++Alien.nextID;
}
}
Alien.nextID = 0;
旧方式のクラスでは、ActorIDとAlienIDが重複してしまい、{id:1~4}ということで、IDが増えてしまいます。
なので、スーパークラスで使っているプロパティ名は、サブクラスでは使わないようにしましょう。
今回は、クラス構文で、スーパークラスでも、actorIdというユニークなプロパティ名にしました。
function myFunction5_39_02() {
/** クラスScene */
class Scene {
constructor(context, width, height, images) {
this.context = context;
this.width = width;
this.height = height;
this.images = images;
this.actors = [];
}
register(actor) {
this.actors.push(actor);
}
}
/** クラスActor */
class Actor {
constructor(scene, x, y) {
this.scene = scene;
this.x = x;
this.y = y;
this.actorId = ++Actor.nextID;
scene.register(this);
}
}
Actor.nextID = 0;
/**
* クラスSpaceShip
* @extends Actor
*/
class SpaceShip extends Actor {
constructor(scene, x, y) {
super(scene, x, y);
this.points = 0;
this.type = 'spaceShip';
}
}
/**
* クラスAlien
* @extends Actor
*/
class Alien extends Actor {
constructor(scene, x, y, direction, speed, strength) {
super(scene, x, y);
this.direction = direction;
this.speed = speed;
this.strength = strength;
this.damage = 0;
this.alienId = ++Alien.nextID;
}
}
Alien.nextID = 0;
const scene = new Scene('context', 1920, 1000, { spaceShip: 'image1' });
const spaceShip1 = new SpaceShip(scene, 200, 100);
console.log(spaceShip1.actorId); //1
const spaceShip2 = new SpaceShip(scene, 200, 100);
console.log(spaceShip2.actorId); //2
const alien1 = new Alien(scene, 200, 100);
console.log(alien1.alienId); //1
}
まとめ
以上で、「スーパークラスのコンストラクタは、サブクラスのコンストラクタから呼び出す」「スーパークラスのプロパティ名は、決して再利用しない」を2本まとめてお届けしました。
第5回目の輪読会は、ここまでです。
次回輪読会をお楽しみに。