ビールとプリンとプログラミング。

頭の悪いプログラマのぼやき。

javascriptのスコープ

みなさん、お疲れ様です。

ということで、
本日は
Valtech (バルテック) Advent Calendar 2016
投稿記事です。

Webアプリケーションの開発に携わらせて頂いて3年となりました。

Javarista × JSerの私です。

今年はjavascriptの基本的な部分について、
主に研修生の皆様へ向けて書かせて頂きます。

javascriptのスコープ』について。

Javaのスコープを確認

さて、
javascriptと言いながら、まずはJavaでコードを書いてみます。

public class Hoge {  
    public static void main(String[] args) {
        {
            String hoge = "ほげほげ";    // …①
            System.out.println(hoge);   // …②
        }
        System.out.println(hoge);       // …③
    }
}  

Javaの研修をある程度進めた方はお気づきになるかと思います。
このコードをそのままコンパイルしようとすると怒られます。

「シンボルを見つけられません」

はい。
Javaの変数(ローカル変数)はブロック(『{』と『}』)で囲まれた範囲のみが有効となります。

①で宣言した「hoge」は、当然②では有効ですが、③では無効となります。逆に②の位置では新たに「hoge」を宣言することはできませんが、③の位置では「hoge」を宣言することができるというわけです。

ブロックスコープを有効的に使用して、変数はなるべく必要な範囲のみで、限定的に使用していくのが習慣になっているかと思います。

私もそうでした。

そして、その感覚のままjavascriptを書くと、
ちょっとしたことからバクを入れ込んでしまうかもしれないので気をつけましょう!

というのが今日の趣旨です。

javascriptでやってみる

では、先ほどのコードをjavascriptで書き直してみます。

function fuga() {
    {
        var hoge = "ほげほげ";
        console.log(hoge);
    }
    console.log(hoge);
}
fuga();

開発者ツール等で実行してみるとわかるかと思いますが、特に問題なく終了します。
chromeならF12→Consoleタブ→コピペ→Enter☆)

そうです。

javascriptのverで宣言した変数はブロックスコープではないのです。
for文やif文のブロック内で宣言した変数も生き続けます。

function fuga() {
    for(var i = 0; i < 10; i++) {
        // なんらかの処理。
    }  
    console.log(i);
}
fuga();

上の例では、for文でiを宣言しています。
for文を抜けたあと、iをログに表示させようとしているのですが…。
感覚的には、エラーあるいはundefinedになってほしいですね。
ところが、結果は「10」が出力されます。

function fuga() {
    if(true) {
        var msg = "メッセージA";
        alert(msg);
    }

    // (長過ぎる処理があったりして…中略)

    var msg;
    // 条件によってメッセージを入れる。
    if(false) {
        msg = "メッセージB"
    } else if(false) {
        msg = "メッセージC"
    } else {
        // どれにも当てはまらない場合はメッセージを入れないよ。
    }

    // 条件によるメッセージが入っている場合のみアラートを出す!
    if(msg !== undefined) {
        alert(msg);
    }
}
fuga();

次は、あえてコードをぐちゃぐちゃにしてみました。
書いてるうちに自分で何書いてるかわからなくなっていくパターンですね。

ありますよね?あるよね?

最初の方で、何らかの条件が「true」の時にメッセージAを表示しています。
今回は強制「true」ですので、実行すると表示されます。

そして、ぐだぐだぐだ~~とコードを書いて、

最後にもう一度、今度は条件によって違うメッセージを表示したい。
でも、どの条件にも当てはまらない場合はメッセージを表示したくない。

そんなコードです。

今回は強制「false」ですので、msgはundefinedのまま。
だからメッセージは表示されない!

はずが・・・、表示されてしまいます。
最初のifブロックで宣言したmsgがそのまま残ってしまっているわけですね。
(根本的に色々だめな気がするがスルー)

本題。javascriptのスコープ

ご覧の通り、javascriptのvarで宣言した変数はブロックスコープではありません。
普通は上記のようなコードは書かないかと思いますので、気づきにくいとは思いますが、
他の人が書いたコードに手を加えた場合や、複雑なプログラムを書いていると、バグを混入させるかもしれませんね。

では、スコープはどこか。

基本的にjavascriptのローカル変数は関数スコープとなります。
つまり、その関数内で有効です。
varで宣言した場合、どの位置で宣言しても、その関数の最初に初期化されます。

function fuga() {
    (function() {
        for(var i = 0; i < 10; i++) {
            // なんらかの処理。
        }
        console.log(i);     // …関数の中なので「10」が表示される
    })();
    console.log(i);         // …関数の外なのでエラー
}
fuga();

変数の有効範囲を限定的にしたい場合は、上記のように関数でラッピングしてあげることで実現できます。

ちなみに、varで宣言しない場合は、グローバル変数となってしまい、プログラム全体から参照できてしまいます。
varのつけ忘れにもご注意を。

function fuga() {
    (function() {
        for(i = 0; i < 10; i++) {   // …よく見るとvarを付け忘れている
            // なんらかの処理。
        }
        console.log(i);
    })();
    console.log(i);         // …グローバル変数となってしまっているので参照可能
}
fuga();

さてさて、長文となって申し訳無いですが、

本番はここからです(笑)

関数スコープなのはわかったが、なんかやりづらいなあ!
と思ってるJavaristaの皆さん。朗報です。

ECMAScript 2015(ES6)では、問題なくブロックスコープを使用することができます。

html5experts.jp

対応ブラウザや、プロジェクトの方針等の壁はあるかと思いますが、
javascriptでもブロックスコープな変数を宣言することができるのです。

var ではなく let や const で変数を宣言することで、ブロックスコープとなります。

function fuga() {
    {
        let hoge = "ほげほげ";
        console.log(hoge);
    }
    console.log(hoge);      // …①
}
fuga();

これは最初の例を var → let に変更してみたコードです。
①の位置でエラーとなるようになりました。
hogeが有効範囲外となったため。)

また、let や const は重複して宣言することもできなくなっているため、バグの混入を未然に防ぎやすくなっています。
let は再代入可能ですが、 const は再代入不可となります。

qiita.com

というわけで

色々と書きましたが、まとめると

  1. varで宣言した変数はブロックスコープではなく関数スコープ
  2. ブロックの代わりに関数でラップすれば有効範囲を限定できる
  3. そんなことせんでも let とか const 使えばええやん

というお話でした。
意外と見落としガチではないでしょうか。javascriptのスコープ。
私だけ???

基本的には const で宣言、変更が必要な場合だけ let で宣言

というのがトレンドらしいですが、
実際にはそうも言ってられないのが現実かと思います。

特にこれから現場に出て、バリバリjavascript書くぞ!という皆様には、
少しvarのスコープを意識してもらえると楽しいJSerライフが待ってるかと思います!

さらに蛇足

そして最後に。

説明の中で、さらっと

(function() { ~~~ })();  

のような即時関数を使用しましたが、
これも研修直後、現場出てすぐは「???」となるポイントかと思います。
(少なくとも私には)

お暇な時にでも調べてみるとよいかと思います。

深掘りすると、深すぎてハマります。

d.hatena.ne.jp

以上!

12月3日にバトンタッチ(ノ ̄^ ̄)ノオリャ!≡≡≡≡☆┻━┻() ̄□ ̄)/ガコ!!