最近の行儀のよい JavaScript の書き方

  • 2468
    いいね
  • 2
    コメント

JavaScriptは移り変わりの早い言語です。
もう1年以上経っていますし、記事のメンテもちゃんとできていないので、消し線を入れることにしました。

参考程度のために記事は一応残しますが、より新しい情報を読まれることをお勧めいたします。

はじめに
---

最近では JavaScript の実行環境はブラウザに限りません。(node.js, Web Workers)
また、旧来のような <script> 経由でのロードもとうに古くなっています。今は CommonJS スタイルで、require を用いたモジュールのロードを行なうことがより良いとされています。

ですから、次のようなことは改める必要があります。

- var YourModule = {}; などとして、外部から YourModule.hoge(); などと呼び出す書き方
- this === window だと思うこと

今回は、これらに対応出来るような行儀の良い JavaScript モジュールの書き方を、uupaa 氏の WebModulePattern から端折りつつご紹介します。

ちなみに JavaScript あまり書かないのでほとんど自分用の勉強メモです。

書き方

まず、全体を (function(){...})(); で囲みます。
これで var がグローバルを汚染しないようになります。

(function() {
    // ...
})();

"use strict"; を宣言しておきます。 (use strict の効用: http://qiita.com/Jxck_/items/fe8a1c49cac717e52ae3#js-%E7%B7%A8)

(function() {
    "use strict";

    // ...
})();

次に、ブラウザでも Web Worker でも node.js でも使えるような書き方でグローバルオブジェクトを取得します。

(function(global) {
    "use strict";

    // ...
})((this || 0).self || global);

この idiom で、ブラウザであれば window, Web Workers であれば WorkerGlobalScope, node であれば global を取得出来ます。
詳しくは WebModuleIdiom を参照ください。

補足:

global 汚染が出来てしまいますが、global.XX = XX といった書き方をしない限り、グローバル空間を触れないという違いがあります。
(function(){})(); で囲わないと、var XX = XX; とするだけでグローバルが汚染されます。

この中で、実行環境を特定したい場合は、次の idiom を使います。

var isBrowser = "document" in global;
var isWebWorkers = "WorkerLocation" in global;
var isNode = "process" in global; 

次に、CommonJS スタイルでの require に対応するため、module.exports にモジュールをエクスポートします。
最近では node.js だけではなく、ブラウザでも browserify や webpack を利用して require を使えるため、ブラウザだけの対応でもエクスポートを行ないます。

if ("process" in global) {
   module.exports = YourModule;
}

最後に、CommonJS スタイルでない通常の <script> 読み出しに対応する為、global オブジェクトに Module を突っ込みます。

global["YourModule"] = YourModule;

全体

(function(global) {
    "use strict;"

    // Your Module
    function YourModule() {
        // ...
    };

    // Exports
    if ("process" in global) {
        module["exports"] = YourModule;
    }
    global["YourModule"] = YourModule;

})((this || 0).self || global);

これで最低限まともにエクスポートされるコードが完成しました。
長いように見えますが、過度期なので仕方がありません。
テンプレとしてどこかに保持しておけば良いでしょう。

ヘッダーと実装の分離

JS には .h がありません。
ですから、ソースを読む側からするとどんなメソッドがあるかを知るだけでも一苦労ですね…

YourModule.prototype.someMethod = function() {...} とすると、実装も宣言も全て一緒くたに書かれてしまうので分離します。

補足:

ややこしいですが someMethod = function() {} と書くと関数自体が無名関数になってしまい、プロファイラ等での追跡が困難になるなどの欠点もあります。

実装は function method() の関数宣言のスタイルで行い、YourModule.prototype.method = method; としてヘッダーとします。

// Class
function YourModule() {
}

// Header
YourModule["prototype"]["constructor"] = YourModule;
YourModule["prototype"]["method"] = method;

// Implementation
function YourModule_method(someArg) {
    // ...
}

このままでは Header を見ても引数が分からないので、コメントで足します。

YourModule["prototype"]["method"] = method; // YourModule#method(someArg:any):void

コメントのスタイルは好みで何でも良いです。ちなみにこれは同じく uupaa 氏作の Help.js スタイルです。

まとめ

以上、次のようなスタイルで書いていけば行儀の良いモジュールの書き方が出来ます。

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

すごく idiomatic ですが、テンプレだと思えば…

Further Reading

この書き方の原典です。より詳しく乗っています。(実は、WebModulePattern では global に突っ込む書き方にもう一工夫あります。端折ってしまいました。)
-> WebModulePattern · uupaa/WebModule Wiki

最近の JS を知りたい場合は
-> IE10 以下を切る場合の JavaScript チェックリスト

Browserify についてサクッと知りたい場合は
-> Browserify: それはrequire()を使うための魔法の杖

References