JavaScriptでES5のクラスからES6のモジュールへ書き換えする方法

プログラミング
スポンサーリンク

はじめに

こんにちは!

最近 JScript (ES3) 用で書いたスクリプトを HTML5用へ書き換えしています。そこで、 ECMAScript を ES5 から ES6 のモジュール(ES6 Modules)への書き換え方法について、少しまとめてみました!

ここで解説した記載方法は、2018/10/14時点で最新の Microsoft Edge / Google Chrome で動作することを確認しています。Internet Explorer 11 は残念ながら未対応となります。

Babelや、TypeScriptを導入しても良いと思いますが、言語してどのように書けるか勉強しておきましょう。

クラス

一番大きな変更点であるクラスの記載方法の変更の解説です。

ES5 のクラス宣言の場合

ES5でのクラスの記載方法はいくつかあるのですが、
今回、以下のような記載方法を例とします。

"use strict";

// ClassA
var ES5ClassA = function(arg) {
	// コンストラクタ
	console.log("call constructor(ES5 ClassA)");
	this.myMethod();
};

ES5ClassA.prototype.myMethod = function() {
	console.log("call myMethod(ES5 ClassA)");
	this.x = "myMethod A";
};

ES5ClassA.staticMethod = function() {
	console.log("call staticMethod(ES5 ClassA)");
};

// ClassA を継承した ClassB
var ES5ClassB = function(arg) {
	console.log("call constructor(ES5 ClassB)");
	// 親のクラスを保存
	this.superForClassB = ES5ClassA;
	// 親のコンストラクタを呼ぶ
	this.superForClassB.call(this, arg);
	console.log(this.x);
	// 親のメソッドを呼ぶ
	this.superForClassB.prototype.myMethod.call(this);
	console.log(this.x);
};

// メソッドをコピーする(staticの継承は不可能)
ES5ClassB.prototype = Object.create(ES5ClassA.prototype);

// メソッドをオーバーライド
ES5ClassB.prototype.myMethod = function() {
	console.log("call myMethod(ES5 ClassB)");
	this.x = "myMethod B";
};

new ES5ClassA();
new ES5ClassB();

このクラスを実行すると、以下になります。

call constructor(ES5 ClassA)
call myMethod(ES5 ClassA)
call constructor(ES5 ClassB)
call constructor(ES5 ClassA)
call myMethod(ES5 ClassB)
myMethod B
call myMethod(ES5 ClassA)
myMethod A

元々、JavaScriptはプロトタイプベースで、
クラスのようなオブジェクトも作れるよという言語のため
少し無理やり感があるコードとなっています。

また、クラスAを継承したクラスBでは、
親のコンストラクタやメソッドを呼び出す場合に、
call を利用しなければいけません。

ES6 のクラス宣言の場合

ES6 ではクラス文に対応したため、以下のような記載ができます。

// ClassA
class ES6ClassA {
	
	// コンストラクタ
	constructor(arg) {
		console.log("call constructor(ES6 ClassA)");
		this.myMethod();
	}
	
	myMethod() {
		console.log("call myMethod(ES6 ClassA)");
		this.x = "myMethod A";
	}
	
	static staticMethod() {
		console.log("call staticMethod(ES6 ClassA)");
	}
}

// ClassA を継承した ClassB
class ES6ClassB extends ES6ClassA {
	
	// コンストラクタ
	constructor(arg) {
		console.log("call constructor(ES6 ClassB)");
		// 親のコンストラクタを呼ぶ
		super(arg);
		console.log(this.x);
		// 親のメソッドを呼ぶ
		super.myMethod();
		console.log(this.x);
	}
	
	myMethod() {
		console.log("call myMethod(ES6 ClassB)");
		this.x = "myMethod B";
	}
}

new ES6ClassA();
new ES6ClassB();

このクラスを実行すると、以下になります。

call constructor(ES6 ClassA)
call myMethod(ES6 ClassA)
call constructor(ES6 ClassB)
call constructor(ES6 ClassA)
call myMethod(ES6 ClassB)
myMethod B
call myMethod(ES6 ClassA)
myMethod A

ES6では上記の通り、非常にシンプルに書けます。
super を利用できるのも楽ですね。

なお、"use strict"; がないのは、
モジュールとしてロードするとStrict Modeになるため省略しました。

static メソッドの継承について

上記で例として挙げたES5とES6のスクリプトは注意点があります。

結論から言うと
ES5 では継承先の ES5ClassB には static メソッド staticMethod が継承されていません。
ES6 では継承先の ES6ClassB には static メソッド staticMethod が継承されています。

ES5 でクラスを継承する場合は、
prototype のコピーは簡単にできるのですが、
static メソッドのコピーは簡単に出来そうもなかったので省略しました。

多分やろうと思えばできるのでしょうが ES5 は面倒そうです。

定数クラス

Java でいう 定数クラス、enumについて、
ES5 から ES6 への書き換え方法を解説します。

ES5 の定数クラスの場合

先ほどのサンプルに定数を入れてみました。

"use strict";

// コンストラクタ
var ES5ClassA = function(arg) {
};
// メソッド
ES5ClassA.prototype.myMethod = function() {
};
// static メソッド
ES5ClassA.staticMethod = function() {
};
// 定数
ES5ClassA.NUMBER = {
	ONE : 1,
	TWO : 2
};

static メソッドと似たように、
関数を代入するのではなく、値を代入すればよいだけです。

ES6 の定数クラスの場合

案1 getter を利用する

class ES6ClassA {
	
	// コンストラクタ
	constructor(arg) {
	}
	
	// メソッド
	myMethod() {
	}
	
	// static メソッド
	static staticMethod() {
	}

	// 定数
	static get NUMBER() {
		return NUMBER;
	}
}

// 定数
const NUMBER = {
	ONE : 1,
	TWO : 2
};

案2 ES5のように別途プロパティとして代入する

class ES6ClassA {
	
	// コンストラクタ
	constructor(arg) {
	}
	
	// メソッド
	myMethod() {
	}
	
	// static メソッド
	static staticMethod() {
	}
}

// 定数
ES5ClassA.NUMBER = {
	ONE : 1,
	TWO : 2
}

案1のほうが、クラス内で定義しているので、
綺麗な感じはしますが。2度記載するため、煩雑な感じがします。
getterに対して、setterがないのも気になります。

HTMLでの外部js読み込み

ES6 の Modules 機能を利用しましょう。
HTMLで外部 js を使用する読み込み方を変更します。

ES5 の外部jsの読み込み方法の場合

<!DOCTYPE html>
<html>
<head>
<title>Title</title>
<meta charset="utf-8">
<script src="./ClassA.js" charset="utf-8"></script>
<script src="./ClassB.js" charset="utf-8"></script>
<script src="./ClassC.js" charset="utf-8"></script>
<script src="./main.js" charset="utf-8"></script>
</head>
<body>
<script>
	...
</script> 
</body>
</html>

ES5ではjs内部で他のjsを読み込むことが出来ないため、
html内に1つ1つファイルを記載する必要があります。

ES6 の外部jsの読み込み方法の場合

scriptに、type="module" をつける必要があります。

<!DOCTYPE html>
<html>
<head>
<title>Title</title>
<meta charset="utf-8">
<script type="module" src="./main.js" charset="utf-8"></script>
</head>
<body>
<script type="module">
	...
</script> 
</body>
</html>

ES6では、後述のimport/export文のおかげで、
JavaScript内から他のJavaScriptを読み込むことが可能となるため、
シンプルになります。

import / export 対応

ES6の特徴であるjs内での外部js読み込み対応です。

この対応を行うためには、 import / export を追加する必要があります。

  • import は、外部の js 内のオブジェクトを読み込む
  • export は、外部に公開するオブジェクトを設定できる

ここで言うオブジェクトとは、変数であったり定数であったり、
配列であったり、クラスであったり、何でも可能です。

少し解説していきたいと思います。

export の対応方法

js内でオブジェクトを公開する場合は、export を使用します。

export の書き方

書き方は、色々ありまして、以下のようにかなり自由度があります。

// オブジェクトを公開設定にしたり
var a = 1;
export a;
// 宣言時に公開宣言したり
export let a = 1;

オブジェクトを1つのみ外部公開する

以下のように default を使用します。

const a = 1;
export default a;
export default class A {
	constructor() {
	}
}
// 定数も後で定義可能
A.ONE = 1;

オブジェクトを2つ以上外部公開する

2つ以上の場合は、以下のように記載します。
defaultも1つのみ設定が可能です。

const a = 1;
let b = 1;
export { a, b };

コンテナを利用して1つのみ外部公開する

複数のクラスを、1つのjsにまとめたい場合は、

class A {
...
}
class B {
...
}
export default const MyLib = {
	A: A,
	B: B
}

上記のように1つのオブジェクトに包み込みコンテナを作成し、
そのオブジェクトを公開しても良いです。

import の対応方法

import ... from *.js でロードします。
ファイルパスは、jsファイルがある場所からの相対パスになります。

1つのみexportされた外部jsを読み込む

1つのオブジェクトのみ default で公開しているjsを読み込むには、
以下のようにします。

import MyClass from "./libs/MyClass.js";

上記のように書くことで、MyClass.jsでdefault公開したオブジェクトを、
MyClass というオブジェクトで利用が可能となります。

名前衝突を避けるために、
オブジェクト名を変更しても構いません。

import Another from "./libs/MyClass.js";

2つ以上exportされた外部jsを読み込む

2つ以上exportしている場合は、以下のようになります。

import { MyClass1, MyClass2 } from "./libs/MyClass.js";

ここで、しっかりクラス内で出力したオブジェクト名に合わせる必要があります。
従って、名前を変更する場合、利用側の名前も全変更しないといけません。

名前衝突する場合は、as を使用します。

import { MyClass1 as Another1, MyClass2 as Another2 } from "./libs/MyClass.js";

export されたものをすべて利用する場合は、* を使用します。

import * as MyLib from "./libs/MyClass.js";

これで、MyLib.MyClass1 、MyLib.MyClass2 が利用可能となります。

おわりに

今回、クラスの書き換え方法に焦点を絞って、
ES5からES6への変換方法について解説を行いました。

ES6の方が見た目がすっきりする上、
import / export によって、クラスの依存関係が
クラス内ではっきり定義できるため、
非常に書きやすく生産性が上がります。

Edgeでさえも対応しているため、何か目的がない限り
ES6に変換していくといいかとおもいます。

以上、ありがとうございました。

コメント

タイトルとURLをコピーしました