ES6 a.k.a. ECMAScript 2015が承認されたので
JavaScriptのモジュールとクラスについて
ブラウザJavaScript(以下、ブラウザJS)からの目線で総括してみます。
#モジュール
##ブラウザJS(モジュールなし)
ES5以前にモジュールという仕様はないので
グローバルオブジェクト作成し
名前空間パターン
var obj = obj || {}
で
JSファイルの分割結合をできるように。
// foo.js
var myProject = myProject || {};
myProject.Foo = function(){};
// bar.js
var myProject = myProject || {};
myProject.Bar = function(){};
// module.js
var myProject = myProject || {};
myProject.Module = function() {};
myProject.Module.prototype.init = function(){
this.foo = new myProject.Foo();
this.bar = new myProject.Bar();
};
// main.js
var Module = myProject.Module;
var module = new Module();
module.init();
参考:JavaScriptパターン
##AMD
- ビルドすることなく開発できる
// module.js
define(["foo", "bar"], function(Foo, Bar){
var foo;
var bar;
function Module() {}
Module.prototype.init = function(){
foo = new Foo();
bar = new Bar();
}
});
// main.js
var Module = require("module");
var module = new Module();
module.init();
###ブラウザJSでの利用法
- RequireJS (scriptタグで読み込み)
##CommonJS(Modules/1.0)
- ビルド前提
- Node.jsで採用されているサーバサイドJS標準仕様
//module.js
var Foo = require("foo");// foo.js
var Bar = require("bar");// bar.js
var foo, bar;
function ModuleCls(){}
ModuleCls.prototype.init = function(Foo, Bar){
foo = new Foo();
bar = new Bar();
}
module.exports = ModuleCls
// main.js
var Module = require("module.js");
var module = new Module();
module.init();
###ブラウザJSでの利用法
- npm + Browserify
- npm + webpack
参考:exports と module.exports の違い
UMD(Universal Module Definition)
AMDとCommonJSを考える より引用
モジュール定義のスタイルを統一的に書けるようにしようという試みが、UMD。
UMDでは、AMDスタイル, CommonJSスタイル, そしてグローバル汚染スタイル(勝手に命名)のどの読み込み方式からでもモジュールを使えるようなモジュール定義方法をまとめている。
// some-umd-module.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.someUMDModule = factory();
}
}(this, function () {
return "hello world";
}));
ブラウザJS + CommonJS
(function(global) {
"use strict;"
// Class ------------------------------------------------
function YourModule() {
};
// Header -----------------------------------------------
YourModule["prototype"]["method"] = YourModule_method; // YourModule#method(someArg:any):void
// Implementation ---------------------------------------
function YourModule_method(someArg) {
// ...
}
// Exports ----------------------------------------------
if ("process" in global) {
module["exports"] = YourModule;
}
global["YourModule"] = YourModule;
})((this || 0).self || global);
ブラウザJS + AMD + CommonJS
- JavaScriptのモジュールの書き方より引用
- Promiseライブラリ Qの実装
- jQueryに似た書き方
(function(definition){// 定義する関数を引数にとる
// ロードされた文脈に応じてエクスポート方法を変える
// CommonJS
if (typeof exports === "object") {
module.exports = definition();
// RequireJS
} else if (typeof define === "function" && define.amd) {
define(definition);
// <script>
} else {
MyModule = definition();
}
})(function(){// 実際の定義を行う関数
'use strict';
var MyModule = function MyModule(){};
MyModule.prototype = {
//...
}
// モジュールのエクスポート
return MyModule;
});
参考:小さく始める isomorphic module pattern
##ES6 Modules
- 対応ブラウザはまだない[参考]
- ES6仕様の中でも実装されるのが遅くなるとのこと
//module.js
var foo;
var bar;
export var init = function(Foo, Bar){
foo = new Foo();
bar = new Bar();
}
import module from "module";
module.init();
#クラス
##プロトタイプパターン
- ES5未満時代の定番
var MyClass = function(){
this.id = 0;
this.name = "test name";
};
MyClass.prototype.foo = function() {
return this.id;
};
MyClass.prototype.bar = function() {
return this.name;
};
var myClass = new MyClass();
myClass.foo();
myClass.bar();
//継承(JavaScriptパターン P132)
var inherit = (function() {
var F = function () {};
return function (C, P) {
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
C.prototype.constructor = C;
};
}());
var OtherClass = function(){
this.id = 1;
this.name = "test other";
};
inherit(OtherClass, MyClass);
var otherClass = new OtherClass();
otherClass.foo();
otherClass.bar();
参考:JavaScriptパターン
##モジュールパターン
- プロトタイプパターンより簡潔だが、欠点以下2つ
- 継承先オブジェクトが継承元コピーを持つためメモリ使用量が増える
- プロトタイプチェーンを使わずsuper関数相当の取り扱いに難がある
var MyClass = function(){
var id = 0;
var name = "test name";
return {
id: {
set: function(_id) {
id = _id;
}
},
name: {
set: function(_name) {
name = _name;
}
},
foo: function() {
return id;
},
bar: function() {
return name;
}
};
};
var myClass = MyClass();
myClass.foo();
myClass.bar();
//継承(関数型継承)
var OtherClass = function() {
var cls = MyClass();
cls.id.set(1);
cls.name.set("test other");
return cls;
}
var otherClass = OtherClass();
otherClass.foo();
otherClass.bar();
参考:テスト駆動JavaScript
##Object.create(ECMAScript 5〜)
- IE9以上
var myClass =Object.create({}, {
id:{
writable:true,
configurable:true,
enumerable:true,
value: 0
},
name:{
writable:true,
configurable:true,
enumerable:true,
value: "test name"
},
foo:{
configurable:true,
enumerable:true,
get: function(){
return this.id
}
},
bar:{
configurable:true,
enumerable:true,
get: function(){
return this.name
}
}
});
myClass.foo;
myClass.bar;
//継承
otherClass = Object.create(myClass, {
id:{
value: 1
},
name:{
value: "test other"
}
});
otherClass.foo;
otherClass.bar;
参考:テスト駆動JavaScript
##ECMAScript 6 Class
- 主にトランスパイラでビルドして使う
- Chromeで
#enable-javascript-harmony
フラグを有効にすると使えるようになる
"use strict";
class MyClass {
constructor() {
this.id = 0;
this.name = "test name";
}
foo() {
return this.id;
}
bar() {
return this.name;
}
}
var myClass = new MyClass();
myClass.foo();
myClass.bar();
//継承
class OtherClass extends MyClass {
constructor() {
super();
this.id = 1;
this.name = "test other";
}
}
var otherClass = new OtherClass();
otherClass.foo();
otherClass.bar();
#変換(トランスパイラ・ポリフィル等)
Babel : ES6→ES5
- 定番トランスパイラ
- ES6 Class・Module共対応
CommonJS→AMD
- Knockoutの実装
RequireJS : Simplified CommonJS wrapper についてより引用
ちなみに、Knockout のAMD対策はこうなってた。
define(['export', 'require'], function(koExports, amdRequire){
// koExports.* にオブジェクト定義しまくる実装。
// 返り値はない。
});
define(function(require, exports, module) {
//Put traditional CommonJS module content here
});
#まとめ
モジュールの今の主流はCommonJS + Browserifyでしょうか。
クラスは今までのものが無駄に複雑すぎるので
トランスパイラでES6クラスを利用していきたいところ。