JSのレキシカルスコープについて

以下記事の要約になります。

レキシカルスコープ(Lexical Scope)

スコープについては、2つのアプローチがあります。それは、Dynamic ScopeとLexical Scopeの2つです。

JSでは後者を採用しています。

まず、レキシング(lexing)とは何かについて、まとめます。

これは、Tokenizingとも同じものですが、ソースコード内の文字列を解析し、文法的に意味のあるtokenへassignするものを指します。

レキシカルスコープはレキシング時に定義されるスコープです。

言い換えれば、レキシカルスコープは、書き込み時に変数とスコープのブロックが作成される場所に基づいているため、レクサーがコードを処理する時点までに(ほとんど)設定されます。

方法としては、レキシカルスコープをずらすものもありますが、本質的には、そのままの時間(Lex-time)を使うのがベスト・プラクティスになります。

scope look-up

スコープは、最初に一致するものが見つかると探す作業をやめます。

なので、ネストされたスコープの複数のレイヤーで、同じ識別子名を指定することができます。

これを "シャドウイング"(shodowing)と呼びます。

インナー識別子がアウター識別子をshadowするイメージですね。

ただ、このシャドーイングされた変数も、グローバル変数の場合はアクセスが可能です。例えばグローバル変数a があった場合

window.a

のように、グローバルオブジェクトwindowを通して、そのプロパティaとしてアクセスできます。

スコープルックアップは、シャドーイングに関係なく、常にその時点で実行されている最も内側のスコープから開始し、最初のマッチまで外側に/上向きに動き、停止します。

また、関数のlook-upに関しても、同じことがいえます。どこでその関数が呼ばれようと、関数定義がされたそのその場所で、スコープは定義されます。

レキシカル時間のずらし方

レキシカルスコープを修正するには2つの手段があります。この作業はパフォーマンスの低下につながることを留意する必要があります。

1eval

eval関数は文字列を引数にとり、その内容を、evalが実行したその時点で作成されたものとして扱います。

function foo(str, a) {
eval( str );
        // a b共にfoo関数のスコープ内でアクセスされる
console.log( a, b );
}

var b = 2;

foo( "var b = 3;", 1 ); // 1 3

上の例では、文字列の”var b = 3;”はeval関数が呼ばれた時点で、作成されています。

よって、グローバル変数bの値ではない値がログに出力されます。

実際、動的にコードを生成するような機会はあまりありません。また、それによって引き起こされるパフォーマンスの低下も、効率的とは決して言えないものになります。

2with

二つ目の方法はwithです。オブジェクトのpropertyを参照するときに、かんたんに参照ができるものになります。

ただし、withを使ッタその時点で、本来のレキシカルスコープとは全く違うスコープを生成して書き換えてしまいます。

これらの方法(eval, with)はコードの本来持っている最適化機能(レキシング)を無効にするだけで、外をもたらす方法になります。結果、コードの実行が遅くなることに寄与することになります。

つまり2つの関数をコード内に使うのは、やめておいた方がいいと云うことです。

Like what you read? Give Tuyoshi Akiyama a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.