質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.13%

ES6からグローバル汚染を避けるために、すべて即時関数の中に記載しなくてよくなったと聞きますが、疑問点があります。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 6
  • VIEW 7,631
退会済みユーザー

退会済みユーザー

ES6からグローバル汚染を避けるために、すべて即時関数の中に記載しなくてよくなったと聞きました。

ただの{}ですべて処理のグループをとりあえず囲めばブロックスコープになるのでこれにvar以外を使えばそれでよいのでしょうか?
初心者にはただの{}に式が詰まっているのは文法エラーのように見えるのですが、ただの{}というのは問題ないのでしょうか?

またトランスパイルする場合は、このやり方だと、letがvarに代わってしまうので、相変わらず即時関数か、無名関数で囲むしかないという認識でよいでしょうか?
また、一般的に正しい書き方は、トランスパイルする場合は、
まず全体を即時関数で囲み、その後各グループをさらに即時関数で囲んでいくというやり方でよいでしょうか?
即実行したくないものは、ここに入れてはいけないのかという疑問も初心者には出てくるのですが、そもそも即実行しないものなんて実際はないですかね?

・JS本格入門より
>>>
即時命令は使わない
前項で触れた即時関数は、ES2015の環境では要らなくなります。以下のように、該当するコードをブロックでくくり、配下の変数をlet命令で宣言すれば、即時関数と同じ効果を得られるからです。
◉リスト4-20 let_block.js
{
let i = 5;
console.log(i);     // 結果:5
}

console.log(i);     // 変数iはスコープ外なのでエラー
リスト4-18と比べると、ぐんとコードがシンプルになっていると思います。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • kei344

    2017/03/09 21:35 編集

    引用部はわかるように「> 」を行頭に付けてください。(追記:)半角>と半角スペースを行頭にです。PCからの投稿であれば入力部分のテキストエリアの上にツールバーがあるので、それでも出来ます。

    キャンセル

回答 1

checkベストアンサー

+9

疑問の通り、Babelを使用してES5にトランスパイルした場合は、変数の隔離は正常に行われません。本に書いてある方法では問題が発生する場合があります

実例を見てみましょう。下記のような二つのカウンタがあり、それぞれの処理するためのJavaScriptが別々だったとします。

test.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>カウンタ</title>
  </head>
  <body>
    <h1>カウンタ</h1>
    <p>
      A:
      <button id="a-up">上げる</button>
      <input id="a-counter" type="number" value="0">
      <script src="a.js"></script>
    </p>
    <p>
      B:
      <button id="b-up">上げる</button>
      <input id="b-counter" type="number" value="0">
      <script src="b.js"></script>
    </p>
  </body>
</html>

a.es

"use strict";
{
    const aCounter = document.getElementById("a-counter");
    const aUpButton = document.getElementById("a-up");
    let count = parseInt(aCounter.value);
    aUpButton.addEventListener("click", function() {
        count++;
        aCounter.value = count;
    });
}

b.es

"use strict";
{
    const bCounter = document.getElementById("b-counter");
    const bUpButton = document.getElementById("b-up");
    let count = parseInt(bCounter.value);
    bUpButton.addEventListener("click", function() {
        count++;
        bCounter.value = count;
    });
}

ES6がわからないブラウザのためにBabelで変換(babel --presets=latest a.esとかで)するとこうなります。

a.js

"use strict";

{
    var aCounter = document.getElementById("a-counter");
    var aUpButton = document.getElementById("a-up");
    var count = parseInt(aCounter.value);
    aUpButton.addEventListener("click", function () {
        count++;
        aCounter.value = count;
    });
}

b.js

"use strict";

{
    var bCounter = document.getElementById("b-counter");
    var bUpButton = document.getElementById("b-up");
    var count = parseInt(bCounter.value);
    bUpButton.addEventListener("click", function () {
        count++;
        bCounter.value = count;
    });
}

実際動かしてみればわかりますが、AとBのカウンタの動きが互いに副作用を起こし、独立して動作しません。なぜなら、両方ともグローバルスコープにcountという変数があり、同じ物を使ってしまうからです。

Babelで変換せずにES6としてそのまま読み込んだ場合はうまくいきます。ではBabelのバグなのかというと、バグと言うよりも限界です。Babelは、letやconstのブロックスコープは事前に知っている変数と重複していれば、変数名を変えるなどでブロックスコープをエミュレートできるように処理できます。しかし、独立して存在する別のJavaScriptで変数名が被っているかどうかはわからないため、そのような処理ができません。今回のように別々に存在する場合は、互いにletで宣言した変数とかは知らないので、単純なvarに置き換えてしまいます。

そもそも、その本に書いている方法では関数宣言が対応できません。ES5のときと同じ問題が起きるでしょう。

では、どうするのが良いのかというとモジュールベースで作成するというのがES6らしい方法かと思います。

"use strict";
import './a.es';
import './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からそこら辺は変わってきています)。

某所で「無名関数で囲むな」と私は書いていますが、それはモジュールベースで作ることが前提の話です。この部分の解説を書かなきゃ…。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/10 09:34

    難しいですね。

    cssのimportと同じように下記は、ES6では{}で全体を囲んでおけば済む話を、{}で囲まずすべて、別ファイルにして、
    最後に一つのファイルにまとめてしまえば、babelか何かがが勝手にカプセル化してくれるのでしょうか?

    "use strict";
    import './a.es';
    import './b.es';



    >>>
    何かよくわからないような気もしますが、import/exportを使ったモジュールベースなら、モジュールごとに名前空間がわかれるので、無名関数で囲まなくてもいい、とだけまずは覚えておけばいいかと思います。

    難しくて内容はわからないので、上記の認識でよろしいでしょうか?

    それかブラウザが完全にES6をサポートするまで即時関数でとりあえず囲んでおくかですね。
    もう少しでしょうから

    キャンセル

  • 2017/03/10 19:16 編集

    > cssのimportと同じように

    ES6のimportはCSSの@importとは動作が全く異なります。同じではありません。

    > ES6では{}で全体を囲んでおけば済む話を、

    {}で囲んでもグローバル汚染の対策になっていません。回答の前半はその説明です。

    > 最後に一つのファイルにまとめてしまえば、

    一つにまとめるといっても、単純に結合することではありません。importのような仕組みを使ってまとめた場合だけ有効です。

    > babelか何かがが勝手にカプセル化してくれるのでしょうか?

    browserifyやwebpackが無名関数で囲った形に変換します。実際どうなるのかは自分で試して見てください。

    > 上記の認識でよろしいでしょうか?

    そうです。

    > それかブラウザが完全にES6をサポートするまで即時関数でとりあえず囲んでおくかですね。

    IE対応を捨てない限り、ES6をそのまま使える日は来ません。

    キャンセル

  • 2017/03/10 21:23

    >>>
    ES6では{}で全体を囲んでおけば済む話を、

    ES6でbabelでトランスパイルをしなければという意味です。



    >>>
    一つにまとめるといっても、単純に結合することではありません。importのような仕組みを使ってまとめた場合だけ有効です。

    gulpで一つにまとめているんですが、それではだめでimportという特別な方法でないとだめなのですね。



    >>>
    IE対応を捨てない限り、ES6をそのまま使える日は来ません。

    23年まではbabelでES5にしないといけないのか。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.13%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • JavaScriptに関する質問
  • ES6からグローバル汚染を避けるために、すべて即時関数の中に記載しなくてよくなったと聞きますが、疑問点があります。