疑問の通り、Babelを使用してES5にトランスパイルした場合は、変数の隔離は正常に行われません。本に書いてある方法では問題が発生する場合があります。
実例を見てみましょう。下記のような二つのカウンタがあり、それぞれの処理するためのJavaScriptが別々だったとします。
test.html
HTML
1<!DOCTYPE html>
2<html>
3 <head>
4 <meta charset="utf-8" />
5 <title>カウンタ</title>
6 </head>
7 <body>
8 <h1>カウンタ</h1>
9 <p>
10 A:
11 <button id="a-up">上げる</button>
12 <input id="a-counter" type="number" value="0">
13 <script src="a.js"></script>
14 </p>
15 <p>
16 B:
17 <button id="b-up">上げる</button>
18 <input id="b-counter" type="number" value="0">
19 <script src="b.js"></script>
20 </p>
21 </body>
22</html>
a.es
ES6
1"use strict";
2{
3 const aCounter = document.getElementById("a-counter");
4 const aUpButton = document.getElementById("a-up");
5 let count = parseInt(aCounter.value);
6 aUpButton.addEventListener("click", function() {
7 count++;
8 aCounter.value = count;
9 });
10}
b.es
ES6
1"use strict";
2{
3 const bCounter = document.getElementById("b-counter");
4 const bUpButton = document.getElementById("b-up");
5 let count = parseInt(bCounter.value);
6 bUpButton.addEventListener("click", function() {
7 count++;
8 bCounter.value = count;
9 });
10}
ES6がわからないブラウザのためにBabelで変換(babel --presets=latest a.es
とかで)するとこうなります。
a.js
JavaScript
1"use strict";
2
3{
4 var aCounter = document.getElementById("a-counter");
5 var aUpButton = document.getElementById("a-up");
6 var count = parseInt(aCounter.value);
7 aUpButton.addEventListener("click", function () {
8 count++;
9 aCounter.value = count;
10 });
11}
b.js
JavaScript
1"use strict";
2
3{
4 var bCounter = document.getElementById("b-counter");
5 var bUpButton = document.getElementById("b-up");
6 var count = parseInt(bCounter.value);
7 bUpButton.addEventListener("click", function () {
8 count++;
9 bCounter.value = count;
10 });
11}
実際動かしてみればわかりますが、AとBのカウンタの動きが互いに副作用を起こし、独立して動作しません。なぜなら、両方ともグローバルスコープにcount
という変数があり、同じ物を使ってしまうからです。
Babelで変換せずにES6としてそのまま読み込んだ場合はうまくいきます。ではBabelのバグなのかというと、バグと言うよりも限界です。Babelは、letやconstのブロックスコープは事前に知っている変数と重複していれば、変数名を変えるなどでブロックスコープをエミュレートできるように処理できます。しかし、独立して存在する別のJavaScriptで変数名が被っているかどうかはわからないため、そのような処理ができません。今回のように別々に存在する場合は、互いにletで宣言した変数とかは知らないので、単純なvarに置き換えてしまいます。
そもそも、その本に書いている方法では関数宣言が対応できません。ES5のときと同じ問題が起きるでしょう。
では、どうするのが良いのかというとモジュールベースで作成するというのがES6らしい方法かと思います。
JavaScript
1"use strict";
2import './a.es';
3import './b.es';
これをbrowserifyやwebpackとBabelを組み合わせて変換します。そして、これだけをブラウザで読み込むのです。
import/export文はES6に含まれますが、モジュールファイルをどう探すのか等はまだ策定されていません。しかし、先行してBabelなどではCommonJS風require()
などに変換してくれるので、それらをまとめられるbrawserifyやwebpackを使えばES5向けのJavaScriptにすることができます。これらモジュールベースでは、完全に名前空間がわかれているため、varを使おうがブロックがなかろうが問題ありません。唯一互いに副作用を及ぼすのは、グローバルオブジェクト(ブラウザならwindow
のこと)のプロパティとして扱う場合(いわゆるグローバル変数)だけです。
何かよくわからないような気もしますが、import/exportを使ったモジュールベースなら、モジュールごとに名前空間がわかれるので、無名関数で囲まなくてもいい、とだけまずは覚えておけばいいかと思います。
【補足】
Rails等で使われるsprocketsには同じ問題があります。元々はCoffeeScriptを使う文化(CoffeScriptはデフォルトで変換時に無名関数で囲む)だったので誰も気にしなかったのかも知れません(Rails 5.1からそこら辺は変わってきています)。
某所で「無名関数で囲むな」と私は書いていますが、それはモジュールベースで作ることが前提の話です。この部分の解説を書かなきゃ…。