変数の隠蔽

先に示した setTimeout() を利用したカウントダウンプログラムを見てみよう。

countdown.js(再掲)

// 例: 変数の隠蔽化
var count = 10, tmID, infobox = document.getElementById("timeout");
function countDown() {
  if (--count == 0) {
    infobox.innerHTML = "ぼかーん";
  } else {
    infobox.innerHTML = count + "秒前";
    tmID = setTimeout(countDown, 1000);
  }
}
function startCountDown() {
  tmID = setTimeout(countDown, 1000);
  infobox.removeEventListener("click", startCountDown, false);
  infobox.addEventListener("click", stopCountDown, false);
}
function stopCountDown() {
  clearTimeout(tmID);
  infobox.innerHTML = "停めました。";
  infobox.removeEventListener("click", stopCountDown, false);
}
infobox.addEventListener("click", startCountDown, false);

このプログラムではカウントダウン処理に必要な変数として count, tID, infobox の3つを利用している。 これは JavaScript 処理系のトップレベルの名前空間に居座りグローバル変数となる。 グローバル変数を多用すると、同じWeb文書内で別の JavaScript プログラムをロードした場合にかち合う可能性がある。

個人用途に確実に限定されるプログラムでないかぎり、 グローバルシンボルの多用は避けるべきである。

関数スコープを利用したカプセル化

関数定義内で宣言した変数は、関数内でしか有効にならないことを利用し、 プログラムに必要な一連の変数、関数定義を包含する関数1つに集約することで、 グローバルシンボルはその外側関数1つだけで済ませられる。

規模のあまり大きくない JavaScript プログラムでは以下のように書くことでグローバル変数多用を回避できる。

capsule-timer.js

// 例: 変数を隠蔽した書き方
function CountDown() {
    var count = 10, tmID, infobox = document.getElementById("timeout");
    function countDown() {
	if (--count == 0) {
	    infobox.innerHTML = "ぼかーん";
	} else {
	    infobox.innerHTML = count + "秒前";
	    tmID = setTimeout(countDown, 1000);
	}
    }
    function startCountDown() {
	tmID = setTimeout(countDown, 1000);
	infobox.removeEventListener("click", startCountDown, false);
	infobox.addEventListener("click", stopCountDown, false);
    }
    function stopCountDown() {
	clearTimeout(tmID);
	infobox.innerHTML = "停めました。";
	infobox.removeEventListener("click", stopCountDown, false);
    }
    infobox.addEventListener("click", startCountDown, false);
};
document.addEventListener("DOMContentLoaded", CountDown, false);

くりっくしてね

このように書くことで、グローバル空間には CountDown というシンボルしか残さないため、変数の衝突が回避できる。

さらに進んだカプセル化

無名関数をすぐに呼ぶ書き方がある(jsで試してみること)。

// 通常の無名関数(定義だけ)
(x)=>{print(x);}
// これをすぐに呼ぶには無名関数を括弧で括って関数呼び出しの () をつける。
((x)=>{print(x);})(3);

これを利用し、上記 capsule-timer.js に対して 以下のような書き換えを行うと一切グローバル空間に シンボルを残さない書き方ができる。

(()=>{
  function CountDown() {
    // 上記 function CountDown() の定義をここに書く
  }
  document.addEventListener("DOMContentLoaded", CountDown, false);
})()

上記の最も外側の CountDown 関数定義を無名関数内で行い、その無名関数の末尾でイベントリスナ登録している。 このように定義した例を示す。

capsule-timer2.js

// 例: 変数を隠蔽した書き方
(() => {
  function CountDown() {
    var count = 10, tmID, infobox = document.getElementById("timeout");
    function countDown() {
	if (--count == 0) {
	    infobox.innerHTML = "ぼかーん";
	} else {
	    infobox.innerHTML = count + "秒前";
	    tmID = setTimeout(countDown, 1000);
	}
    }
    function startCountDown() {
	tmID = setTimeout(countDown, 1000);
	infobox.removeEventListener("click", startCountDown, false);
	infobox.addEventListener("click", stopCountDown, false);
    }
    function stopCountDown() {
	clearTimeout(tmID);
	infobox.innerHTML = "停めました。";
	infobox.removeEventListener("click", stopCountDown, false);
    }
    infobox.addEventListener("click", startCountDown, false);
  }
  document.addEventListener("DOMContentLoaded", CountDown, false);
})();

適用例

以後例示するプログラムではグローバルシンボルの軽減をはかった方がよいと 思われるものは、関数スコープに閉じ込めた書き方を示す。 現実的には、第三者が利用することを前提としたライブラリプログラム等では 1つの関数オブジェクトだけに値を持たせるには不十分な場合もあり、 そのようなときは複写して安全に利用できる関数オブジェクトを定義する必要がある。