モダンJavaSciript、変数とスコープについて

最終更新日

最近Adobe社のアプリケーションの自動化でもモダンなJavaScriptが使えるようになってきました。まあ、ExtendScriptのECMA3は1999年にリリースされたバージョンですからES6なUXP系とは15年以上の乖離があるわけでして、ここまで差異があると言語仕様上無視できない部分が存在します。主だったところでは同期非同期の問題が存在しますが、今回はそれ以前の部分の知ってないと面倒くさいことになりがちな変数とスコープについてお届けしておきます。

ES3なExtendScriptとES6等のモダンなJavaScriptでの違いの大きな部分のひとつに変数の取り扱いというのがあります。ExtendScriptではvarを利用した変数定義が基本となりますが、UXP系のモダンなJavaScriptではletやconstといったキーワードが変数宣言に利用可能です。constはその名の通り定数を宣言するためのもので、一度初期化し値を割り当てると、以降は更新や再宣言ができません。また、letやconstはその有効範囲であるスコープもvarで宣言された変数とは異なります。
このあたりの挙動というのは気が付きにくい不具合に繋がりますので、挙動の違いはしっかりと把握しておきたいものです。

基本的なこと

  • varはグローバルスコープまたは関数スコープ
  • letおよびconstはブロックスコープ

変数や定数の定義に使用するキーワードについての特徴を以下に示します。

  • varで宣言された変数は、そのスコープの中で更新・再宣言可能
  • letで宣言された変数は、更新可能だが再宣言不可
  • constで定義された定数は、更新も再宣言もできない

このように各キーワードにはスコープや挙動に違いがあり、使いどころも異なります。

varを使用した変数の初期化
varで宣言された変数は宣言時に自動的にundefinedとして初期化されます。変数宣言が巻き上げ(Hoisting/後述)られた時点で初期化が行われます。

letやconstを使用した変数の初期化
letやconstで宣言された変数または定数は宣言時にvarのような巻き上げは発生しません。有効なスコープにおいても宣言前に変数を使用しようとするとReferenceErrorが発生します。

letは宣言が実行されるタイミングで初期化が行われます。この時に必ずしも値を指定する必要はありません。宣言時に明示的に初期値を与えなかった場合undefinedとして初期化されます。
constは宣言時に必ず初期値を割り当てる必要があります。

変数の巻き上げ
変数の巻き上げとは、変数宣言がスコープの先頭に移動する挙動を指します。これは、コード内で変数が宣言される前であっても有効なスコープ内であれば変数を参照できるということを意味します。

ただし、変数の初期値の割り当ては巻き上げられないため、宣言以前の部分ではundefinedとして扱われます。
変数の巻き上げは、varキーワードを使用して宣言された場合のみで、ES6(ECMAScript 2015)以降ではletやconstキーワードで宣言された変数及び定数は巻き上げは起こりません。

以下に具体的な例を示します:

varを使用した変数の巻き上げ

alert(pi); // 結果: undefined
var pi = 3.14159265;
alert(pi); // 結果: 3.14159265

上記のコードは、実行時に次のように解釈されます

var pi; // 変数宣言が巻き上げられundefinedで初期化される
alert(pi); // 結果: undefined
pi = 3.14159265; // 初期化(値の代入)は巻き上げられない
alert(pi); // 結果: 3.14159265

letキーワードを使用した変数

alert(pi); // エラー: ReferenceError: Can not access pi before initialization
let pi = 3.14159265;
alert(pi);

前述の通り、letで宣言された変数の定義は巻き上げられません。そのため冒頭の参照でリファレンスエラーが発生します。

スコープの違いについて

各変数はそれぞれスコープ(有効範囲)を持ちます。JavaScriptではグローバルスコープ関数スコープブロックスコープの3種類が存在します。この3つのスコープの違いによって変数の取り回しに違いが出ます。
ここでは各キーワードがどのようなスコープを取るのかを具体的に見ていきます。

ブロックスコープ

if (EXPRESSION)
{
  //ブロックスコープ
}

if文等が持つ{}(ブレースで囲まれた部分)がブロックです。このブロックの内側で定義されたlet及びconstはこのブロック内のみ有効なものとなります。

関数スコープ


function ()
{
  //関数スコープ
}


関数もブレースで囲まれたブロックを持ちますが、関数スコープはvarに対しても影響を及ぼします。関数スコープ内でvarを使うと、この関数のブロック内のみ有効な変数となり、関数冒頭まで巻き上げが生じ初期値としてundefinedが割り当てられます。let及びconstに対しては通常のブロックスコープと同様の挙動となります。

グローバルスコープ

スクリプト内でブレースで囲まれた範囲の外がグローバルスコープと考えても差し支えはありません(ちょっと乱暴)。注意が必要な点はvarの場合、グローバルオブジェクトに対して変数名のプロパティを追加します。このため、グローバルスコープでthisのプロパティとして参照可能となります。グローバルスコープ上でletにて定義された変数はグローバルオブジェクトにプロパティを追加することはありません。ここで定義された変数および定数はスクリプト全体から参照可能となります。

以下、幾つかの例をお届けしておきます。

変数がifのブロックスコープで定義されているためにブロックの外で変数を参照できない例

const PI = 3.14159265;
let a = 10;

if (true)
{
  let area = PI * a * a;
}

alert (area); //Reference Error

varを使うとグローバルスコープとなるためブロックの外から参照可能になります。
この例でも変数定義の巻き上げが生じて定数PIの定義前に変数areaが宣言されたことになります。

const PI = 3.14159265;
let a = 10;

if (true)
{
  var area = PI * a * a;
}

alert (area);

ブロックスコープ内で定義される変数aはグルーバルスコープから参照できません。

let flag = false
if (flag)
{
  let a = 0;
}
else
{
  a+=5;
}
alert(a)

varが巻き上げによりグローバルスコープで宣言されるが、初期値が入力されないためalertされる結果はNaNとなります。

let flag = false
if (flag)
{
  var a = 0;
}
else
{
  a += 5; //undefined + 5
  alert(a)
}

for文等のパーレン内で定義された変数はfor文内のブロックスコープで有効になる。

for (let i=0;…)
{
  //iはこのブロックに含まれる。
}

alert(i); //reference error

ブロック内で宣言された変数aをブロック外から参照できないためReferenceErrorとなる

for (let a=0, i=0; i<5 ;i++)
{
  a += i;
}
alert(a);

varはグローバルスコープに巻き上げられるため意図した通り動作します。

for (let i=0; i<5 ;i++)
{
  var a = i;
}
alert(a);

最後に、スコープチェーンを説明しておきます。スクリプトエンジンは現在実行されているブロックスコープで必要な変数が見つけられない場合、一つ上の階層にスコープを移して変数を探します。

//4
var a = "Test";
function testFunc()
{
  //3
  for (let i=0; i<10; i++)
  {
    //2
    if (i>9)
    {
      //1
      alert(a);
    }
  }
}

この例ではif文の1でalertを行う際に変数aを同一ブロック内を検索します。変数aが見つからない為、一つ階層が上の2のブロックへスコープを移して変数を検索します。ここでもみつかりませんから一つ上の階層である3の関数スコープへスコープを移します。最終的には4のグローバルスコープ迄スコープを移して変数aを探します。これがスコープチェーンの基本的な挙動です。

今回は以上なんですけど、最近のモダンなJavaScriptを読む際に注意すべきところは非同期処理であったり、アロー関数の構造であったりするんですけど、自分で書く分にはECMA3に合わせた書き方でも動いちゃいますからアレな話ではあります。そうそう、InDesignのUXPスクリプトのオブジェクトモデルとかコレクションを配列としてあつかう糖衣構文的なやつも無かったりするのでオシッコちびりそうになったりします(^-^;

ten_a

Graphic Designer, Scripter and Coder. Adobe Community Professional.

シェアする