javascriptの即時関数、モジュールパターン、クロージャ

javascriptのスコープはグローバルスコープ関数スコープの2つしかない。規模が大きくなるとグローバル領域が変数だらけになり、そのうち変数名が被って悪さをする。
今回は、この問題を解消するのと、他のjavascriptコードを見ると必ず使われている即時関数モジュール化などについてのメモ。知らないと他人のコードが読めない可能性がある。

即時関数

関数スコープ内のローカル変数は外部からアクセスできない。この性質を利用してグローバル領域の汚染を最小限にする方法として、即時関数がある。関数の定義をカッコ()で括ってしまう。

(function(){
    var name = "taro";
    console.log(name);
})();

外部から引数を入力する場合は以下のようになる

(function(name, age){
    console.log("my name is "+name+". I am "+age+" years old.");
})("taro", 24);

モジュールパターン

jsmodule1.jsに四則演算関数を変数mathに定義し、3D点を管理する関数をgeoに追加する。このモジュールファイルを本体であるapp.jsから、reqiure関数でモジュールをロードして実行するサンプルを以下に示す。

jsmodule1.jsのポイントは、処理先頭で関数を保管する名前空間を作成し、処理最後にmodule.exportsにモジュール本体への参照を設定している。app.js側では、require関数でjsmodule1.jsをロードして関数が正しく実行できていることが確認できる。

// 厳格モード
// 宣言していない変数を使用する。
// 読み取り専用プロパティに書き込む。
// extensible 属性が false に設定されたオブジェクトにプロパティを追加する。
// configurable 属性が false に設定されたプロパティを削除する。
// オブジェクト リテラルでプロパティを複数回定義する。
// 関数でパラメーター名を複数回使用する。
// etc
'use strict';
 
// モジュールの名前空間を作成する
var ModApp = ModApp || {
    math:{
    },
    geo:{
    }
}
 
// ModApp.mathに四則演算メソッドを追加
ModApp.math = (function(){
    var add, minus, multiply, divide;
    add = function(x,y){
        return x + y;
    };
    minus = function(x,y){
        return x - y;
    };
    multiply = function(x,y){
        return x * y;
    };
    divide = function(x,y){
        return x / y;
    };
    return {
        add : add,
        minus : minus,
        multiply : multiply,
        divide : divide
    }
})();
 
// 3D点管理機能を定義
ModApp.geo = (function(){
    var ptlist = new Array;
    var countPt,addPt,getPt;
    countPt = function(){
        return ptlist.length;
    };
    addPt = function(x,y,z){
        var pt = {};
        pt.x = x;
        pt.y = y;
        pt.z = z;
        ptlist.push( pt );  
    };
    getPt = function(i){
        return ptlist[i];   
    };
    return {
        countPt:countPt,
        addPt:addPt,
        getPt:getPt
    }
})();
 
// node.jsモジュールとして登録
// node.jsのrequire関数でオブジェクトが返る
module.exports = ModApp;
// モジュールをロード
var jsmod1 = require( './js_module1' );
 
// 四則演算
var num1 = jsmod1.math.add(3,5);
console.log(num1); // 8
num1 = jsmod1.math.minus(3,5);
console.log(num1); // -2
num1 = jsmod1.math.multiply(3,5);
console.log(num1); // 15
num1 = jsmod1.math.divide(3,5);
console.log(num1); // 0.6
 
// 3D点を追加
num1 = jsmod1.geo.countPt();
console.log(num1); // 0
jsmod1.geo.addPt(0,0,0);
jsmod1.geo.addPt(1,3,5);
jsmod1.geo.addPt(2,2,5);
 
// 試しに別のインスタンスをロードする
var jsmod2 = require( './js_module1' );
 
// jsmod1で追加した3が返る
num1 = jsmod2.geo.countPt();
console.log(num1); // 3
 
var pt1 = jsmod2.geo.getPt(0);
var pt2 = jsmod2.geo.getPt(2);
num1 = jsmod2.math.minus(pt1.x,pt2.x);
console.log(num1); // -2

クロージャに関するメモ

javascriptにはグローバルもしくは関数スコープの2つしかない。なので、グローバル領域の汚染を防ぐために先のようなモジュール化して外部にローカル変数が漏れないようにする。また、javascirptにもガーベージコレクタが存在し、参照されなくなった変数は、いつか自動的に削除される。

クロージャは、閉じたモジュールに穴を開けて、必要なローカル変数の参照を保持して、ガーベージコレクタに削除されないようにする手法のことだと理解している。

var calc = (function(){
    var num = 1 + 3;
})();

上記の関数が実行されると、変数num_localは関数スコープを抜けると削除される。この計算結果を返すだけであれば、以下のようにする。

var calc = (function(){
    var num_local = 1 + 3;
    return { num : num_local };
})();

計算結果の4がcalc.numに代入されて、結果の値が外部からアクセスできる。ただし、この場合はあくまで4という値がcalc.numに代入されているだけで、ローカル変数のnum_localへの参照ではないのでクロージャではない。

var calc = (function(){
    var num_local = 1 + 3;
    return { num : function(){ return num_local; } }; })();

上記のようにローカル変数num_localへの動的アクセスを伴う関数に格納することによって、クロージャが作成される。