小さい頃はエラ呼吸

いつのまにやら肺で呼吸をしています。


JavaScriptを学ぶ上で避けては通れないレキシカルスコープ


photo credit: bk1bennett via photopin cc

はじめに

JavaScriptを学習する上で最も難解ではないかと思うのが、レキシカルスコープ(静的スコープ)とそれを利用したクロージャという仕組みです。両者のうち、レキシカルスコープだけは、なんとなく理解できてきたので、自分なりにまとめてみました。

JavaScript 第6版
JavaScript 第6版
posted with amazlet at 14.02.04
David Flanagan
オライリージャパン
売り上げランキング: 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)∞ はてなブックマーク - 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です。
このように、スコープが関数を評価したときではなく、定義したときに決まるという性質のスコープをレキシカルスコープ(静的スコープ)と呼びます。

理解する上でのポイント
  • 関数ごとにスコープがある。ある関数から別の関数の変数は見えない。
  • 関数のスコープに変数が見つからなかったら、外側のスコープを探しにいく。
  • スコープは、関数の評価(実行)時ではなく、定義時に決まる。