やりたいこと
外部からGETなどで読み込んだHTMLなどに、すでに読み込んだjavascriptが含まれていることがある。
そのときに例外が発生してしまい、処理が中断されるような状況を回避したい。
回避できる問題
- 変数や関数の再定義を回避する
- 公開したくない処理を隠蔽することで、意図せず他の処理に影響を与えることを防ぐ
- 値をグローバルに公開したいとき、スクリプトが呼び出されるたびに初期化されることを防ぐ
実装例
sample.js
// 無名関数を即時実行することで、グローバルスコープに何も残さないようにしている (後述)
(() => {
/**
* 初回だけ初期化して、意図せず値が失われることを防ぐ
* 例としてこの型を使っているが、実際には何でも良い
*
* @type { Set<any> }
*/
const sharedData = (() => {
// すでにデータが存在する場合はそれを返す
if (window.__sharedData) {
return window.__sharedData;
}
// 共有データを保持するためのSetを作成
const sharedData = new Set();
// グローバルスコープに共有データを登録
// windowの中に存在しない名前をつけて、衝突を避けること。
window.__sharedData = sharedData;
return sharedData;
})();
// 外部ファイルに公開する関数群
function addValue(...value) {
value.forEach(v => sharedData.add(v));
}
function getSharedData() {
return sharedData;
}
// 関数をグローバルスコープに登録
window.addValue = addValue;
window.getSharedData = getSharedData;
/**
* 公開しない関数
*/
function hiddenFunction() {
console.log('公開しない関数が実行されました');
}
hiddenFunction();
})();
実行結果
sample.html
<script src="./sample.js"></script>
<script>
addValue("hoge", 1);
console.log(getSharedData());
// => Set(2) {'hoge', 1}
</script>
<!-- 無名関数で実行しているため、定義済みなどのエラーは発生しない -->
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script>
addValue("fuga", 123);
console.log(getSharedData());
// => Set(4) {'hoge', 1, 'fuga', 123}
</script>
<script>
// console.log(sharedData);
// => ReferenceError: sharedData is not defined
// hiddenFunction();
// => ReferenceError: hiddenFunction is not defined
</script>
コンソールには以下のようなログが出力される。
VM555 sample.js:48 公開しない関数が実行されました
index.html:4 Set(2) {'hoge', 1}
VM557 sample.js:48 公開しない関数が実行されました
VM558 sample.js:48 公開しない関数が実行されました
VM559 sample.js:48 公開しない関数が実行されました
VM560 sample.js:48 公開しない関数が実行されました
VM561 sample.js:48 公開しない関数が実行されました
sample.js:48 公開しない関数が実行されました
index.html:17 Set(4) {'hoge', 1, 'fuga', 123}
無名関数を即時実行する理由
初期化をする関数に名前を付けた場合、その時点でグローバルスコープに関数が登録される。
例として以下のようなinit()
を定義した場合、既存のwindow.init
を上書きすることになる。
function init() {
// 初期化処理
}
window.init(); // 動作する
init(); // もちろん動作する
このような問題を回避するために、即時実行関数を使っている。
(() => {
// 初期化処理
})();
// windowオブジェクトに何も登録されない