JavaScript
ECMAScript
es6

JavaScript モジュール・クラス総括(2015年7月)

More than 3 years have passed since last update.

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での利用法


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での利用法

参考: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

(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();

参考:Modules · Babel


クラス


プロトタイプパターン


  • 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();

参考:Learn ES2015 · Babel


変換(トランスパイラ・ポリフィル等)


Babel : ES6→ES5


  • 定番トランスパイラ

  • ES6 Class・Module共対応


CommonJS→AMD

define(['export', 'require'], function(koExports, amdRequire){

// koExports.* にオブジェクト定義しまくる実装。
// 返り値はない。

});

define(function(require, exports, module) {

//Put traditional CommonJS module content here
});


まとめ

モジュールの今の主流はCommonJS + Browserifyでしょうか。

クラスは今までのものが無駄に複雑すぎるので

トランスパイラでES6クラスを利用していきたいところ。


参考

JavaScriptのモジュールシステムの歴史と現状