クラス継承で実行時エラー
クラス継承で、Uncaught ReferenceError: Cannot access 'class' が発生してはまったので、記録として残しておこう。
背景
では、RectやCircleなどの図形クラスを作った。今後もCANVAS描画を行うゲームプログラムを作るなら、図形を扱うクラスを使うことになるだろうと考え、図形関係のクラスを作っていた。
エラーの発生したコード
エラーが起こったコードは以下の通り。複数のファイルで構成している。
/**
* 図形の基点座標のインターフェイス
*/
export interface IPoint {
/**
* 基点のX座標
*/
X: number;
/**
* 基点のY座標
*/
Y: number;
}
import { Line } from './Line';
import { IPoint } from './IPoint';
/**
* 2次元の点を表すクラス
*/
export class Point implements IPoint {
/**
* X座標
*/
X: number;
/**
* Y座標
*/
Y: number;
/**
* コンストラクタ
* @param x X軸座標、default:0
* @param y Y軸座標、default:0
*/
constructor(x: number = 0, y: number = 0) {
this.X = x;
this.Y = y;
}
/**
* この点を始点、p1を終点とする線分を作る。
* @param p1
*/
public LineTo(p1: IPoint) {
return new Line(this.X, this.Y, p1.X, p1.Y);
}
}
import { Point } from './Point';
/**
* 線分(2点を結ぶ直線)を表すクラス
*/
export class Line extends Point {
/**
* 終点X座標
*/
public X2: number;
/**
* 終点Y座標
*/
public Y2: number;
/**
* コンストラクタ
* @param x 始点X座標
* @param y 始点Y座標
* @param x2 終点X座標
* @param y2 終点Y座標
*/
constructor(x: number, y: number, x2: number, y2: number) {
super(x, y);
this.X2 = x2;
this.Y2 = y2;
}
}
Pointは2次元の点を表すクラスである。Lineは線分(2点を結ぶ直線)を表すクラスである。
そして、「点P(X0,Y0)と点Q(X1,Y1)を結ぶ線分L1を作る。」というコードを書くときに、
import { Point } from './Point';
let P = new Point(X0,Y0);
let Q = new Point(X1,Y1);
let L1 = P.LineTo(Q);
としたかったので、LineTo()メソッドを作ったところ、以下のエラーが出るようになった。
LineTo()を追加するまでは、エラーは出ていなかった。
Uncaught ReferenceError: Cannot access 'Point' before initialization
原因っぽいもの
エラーメッセージをそのまま読むと、「Pointを初期化する前にアクセスできない。」である。以下のときに発生したのと同じメッセージだった。
これはつまり、継承関係にある子クラスを親クラスが参照するとおきるのだろうか?
たしかに、親クラスの途中で継承クラスをnewしているので、(newされる)継承クラスから見ると、初期化が完了していないように見える。
しかし、次のように、同じファイル内に2つのクラスを記載したコードではエラーは発生しなかった。
import { IPoint } from './IPoint';
/**
* 2次元の点を表すクラス
*/
export class Point implements IPoint {
/**
* X座標
*/
X: number;
/**
* Y座標
*/
Y: number;
/**
* コンストラクタ
* @param x X軸座標、default:0
* @param y Y軸座標、default:0
*/
constructor(x: number = 0, y: number = 0) {
this.X = x;
this.Y = y;
}
/**
* この点を始点、p1を終点とする線分を作る。
* @param p1
*/
public LineTo(p1: IPoint) {
return new Line2(this.X, this.Y, p1.X, p1.Y);
}
}
export class Line2 extends Point {
/**
* 終点X座標
*/
public X2: number;
/**
* 終点Y座標
*/
public Y2: number;
/**
* コンストラクタ
* @param x 始点X座標
* @param y 始点Y座標
* @param x2 終点X座標
* @param y2 終点Y座標
*/
constructor(x: number, y: number, x2: number, y2: number) {
super(x, y);
this.X2 = x2;
this.Y2 = y2;
}
}
つまり整理すると、
- 2つのクラスに継承関係がある。
- 2つのクラスは別ファイルに記載されており、互いにimportして使用している。
という条件のときに発生するようだ。
これがTypeScript/JavaScriptの仕様上の問題なのか、importをまとめる処理をしているwebpackの仕様上の問題なのか(あるいは不具合なのか)までは、理解できていない。
回避策
継承関係があるクラスをお互いにimportしあう形でコーディングしなければ問題は発生しないように見える。
今回のケースでは、
Line → Point → IPoint
という継承関係だったのが問題なので、
Point → PointBase → IPoint Line → PointBase
という形に変更した。
継承関係があるクラスは1つにまとめて書く方法でも良さそうだが、クラスの規模が大きくなって1クラス1ファイルにしたくなった時に、またトラブってしまいそうである。
そのため、今後は原則としてはこの形でコーディングしていくことにした。
蛇足
行ないたいことは、「2つのPointを(中身を参照することなく)直接使ってLineを作る」なので、Lineのコンストラクタをoverloadを使って以下のように記載すれば、同じようなことは可能になる。(この場合、let L1 = new Line(P,Q); と書けばよい。)
しかし、コンストラクタが難解なコードになるのは避けたかったので、今回はPoint.LineTo()を作るという方法を選んだ。
export class Line extends Point {
/**
* コンストラクタ
* @param x 始点X座標
* @param y 始点Y座標
* @param endx 終点X座標
* @param endy 終点Y座標
*/
constructor(x: number, y: number, endx: number, endy: number);
/**
* コンストラクタ
* @param begin 始点座標
* @param end 終点座標
*/
constructor(begin:Point, end:Point);
construcotr(p1:Point|number, p2:Point|number, p3?:number, p4?:number){
// 中のコードは省略
}
}
コメント