photo credit: bk1bennett via photopin cc
はじめに
JavaScriptを学習する上で最も難解ではないかと思うのが、レキシカルスコープ(静的スコープ)とそれを利用したクロージャという仕組みです。両者のうち、レキシカルスコープだけは、なんとなく理解できてきたので、自分なりにまとめてみました。
オライリージャパン
売り上げランキング: 7,909
レキシカルスコープとは
JavaScriptはレキシカルスコープを採用しているプログラミング言語です。では、レキシカルスコープとはなんなのか?
いろんな人がブログやWebサイトでレキシカルスコープの説明をしてくれていますが、僕が最も分かりやすいと感じたのは、Wikipediaの解説でした。
A { var x; } B { var x; // A内のxとは別物 C { var y; // Cの内側からしか見えない } }
ブロックAの変数xとブロックBの変数xは同じ名前をもつがブロックが異なる為別のものである。またBの中ではCのようにさらに内側の変数を見ることができず、逆にCからはBのxとCのyが見える。
上記で言っていることは、ブロック内で宣言された変数は、ブロックの外側からは見えないということ。ただし、関数Bの内部関数Cからは、関数Bのローカル変数xが見える。なぜそうなるのと考えるのではなく、そういうものだと割り切って考えると、理解が早くなります。
上記の疑似言語をJavaScriptで書くと以下のような感じになります。
function A() { var x = "A"; // 他の関数からはアクセスできない return x; } function B() { var x = "B"; // 関数Aの変数xとは別もの function C() { var y = "C"; return x + y; // 関数Bの変数xにアクセスできる } return C(); } alert(A()); // >>A alert(B()); // >>BC
変数が見つからなかったら外側のスコープに探しにいく
Wikipediaの解説で1つひっかかるとしたら、この記述です。
Bの中ではCのようにさらに内側の変数を見ることができず、逆にCからはBのxとCのyが見える。
なぜこのような動作になるのかというと、JavaScriptでは関数のスコープ内に指定された名前の変数がなかった場合、外側のスコープに探しにいきます。以下のコードの場合、関数Aのスコープ内にxという名前の変数が定義されていないため、外側のグローバルスコープにxを探しにいきます。(この仕組みは、スコープチェーンと呼ばれています。)
内部関数からその外側の関数に定義された変数が見えるのは、このためです。
var x = 'global'; function A() { return x; } alert(A()); //>> global
スコープは関数を評価したときではなく、定義したときに決まる
JavaScriptのスコープは静的なものであり、関数の実行スコープは、実行時のものではなく定義時のものとなる。
JavaScript 第5版 - 8章 関数(3) - (DxD)∞
これを最もわかりやすく説明しているのが、以下のエントリです。
var x = 'global'; var fx1 = function(){ var x = 'local'; return function(){return x} }; var fx2 = fx1(); x -> 'global' fx2() -> 'local'
この例だとfx2()の値がlocalになってるね。
なぜなら外側スコープっていうのは関数を評価した場所じゃなくて、関数を定義した場所の外側なんだ。
上記のコードでは、fx2の実行結果として、f1関数のreturn文のところにある無名関数が返ってきます。その無名関数は、自分のスコープの1つ外側のスコープにある変数xの値を返します。fx2を評価(実行)しているのはグローバルスコープですが、返ってくる値は、グローバルスコープ内に存在するxではなく、無名関数を定義したスコープの1つ外側にある変数xです。
このように、スコープが関数を評価したときではなく、定義したときに決まるという性質のスコープをレキシカルスコープ(静的スコープ)と呼びます。
理解する上でのポイント
- 関数ごとにスコープがある。ある関数から別の関数の変数は見えない。
- 関数のスコープに変数が見つからなかったら、外側のスコープを探しにいく。
- スコープは、関数の評価(実行)時ではなく、定義時に決まる。