本記事のポイント

本ブログはIoT組込みエンジンNEQTOの特徴の一つであるJavaScriptによるNEQTO Engine対応デバイスの開発にフォーカスし、JavaScriptの基礎およびneqto.js特有の拡張機能について連載形式で紹介していきます。

この連載記事では、JavaScriptの基礎となる構文や文法について順番に解説していきます。第1回目の今回は「変数の宣言とスコープ」について解説します。NEQTOユーザーがJavaScript開発を円滑に進めるために活用頂くこと、また、NEQTOに興味をお持ちの方がNEQTOを使用したJavaScript開発について理解を深めて頂くことが本ブログの目的となります。



1. はじめに

第1回は、プログラミングを行う上で必ずと言っても良いほど使用することになる「変数」にフォーカスし、その基本的な性質について理解を深めるため、neqto.jsを使用して詳しく解説していきます。

「neqto.js」は、NEQTO Engine上で動作するJavaScript環境です。ECMAScript 5.1 Editionに対応しています。

2. 変数の宣言

JavaScriptでは変数を宣言する際にvar文を使用します。

var a;

var文は変数を宣言し、それを任意の値に初期化することができます。

var a = 1;

初期値を指定しない場合は、undefinedとなります。

var a;
print(a);
undefined

カンマ区切りで、複数の変数をまとめて宣言することも可能です。

var a, b;
var a = 1, b = 2;

3. スコープ

スコープ」とは定義された変数や関数の適用範囲を示します。スコープには大きく「グローバルスコープ」と「ローカルスコープ」が存在します。グローバルスコープはプログラムの全範囲、ローカルスコープはプログラム内のある特定範囲を示します。

具体的にJavaScriptコードで説明します。下記をご覧ください。プログラム直下はグローバルスコープに該当し、そこで定義された変数や関数はあらゆるスコープから参照可能となります。一方、関数の中はローカルスコープに該当し、そこで定義された変数や関数はその関数の中でのみ参照可能となります。なお、グローバルスコープで定義された変数を「グローバル変数」、ローカルスコープで定義された変数を「ローカル変数」と呼びます

//Global
var a = 'global variable';
function func() {
  //Local
  var b = 'local variable';
}

4. neqto.jsによる動作検証

neqto.jsの動作環境について詳しく知りたい方は、下記のリンク先を確認してください。

NEQTO Hello World!

① グローバル変数 グローバルスコープで宣言した変数aはどのスコープからも参照可能となります。

var a = 1;

function func() {
 print('localScope: ' + a);
}
func();

print('globalScope: ' + a);
localScope: 1
globalScope: 1

② ローカル変数

関数下のローカルスコープで宣言した変数aはローカルスコープ内からは参照可能ですが、グローバルスコープからは参照でないため、ReferenceErrorが発生します。

function func() {
  var a = 1;
  print('localScope: ' + a);
}
func();

print('globalScope: ' + a);
localScope: 1
[system][error]Script Failed (ReferenceError: a is not defined)

{ }で閉じられた領域のスコープ

{ }で囲まれている領域は「ブロック」と呼ばれ、主にif...elsefor文と組み合わせて使用されます。ブロック下で変数を宣言した場合、そのスコープはブロックの中ではなく、ブロックを含んでいるスコープとなります。

グローバルスコープにブロックを置いた場合、ブロック内で宣言された変数はグローバル変数となります。

if(true) {
  var a = 1;
  print('block: ' + a);
}

print('globalScope: ' + a);
block: 1
globalScope: 1

関数下のローカルスコープにブロックを置いた場合、ブロック内で宣言された変数はその関数のローカル変数となります。

function func() {
  if(true) {
    var a = 1;
    print('block: ' + a);
  }
  print('localScope: ' + a);
}
func();

print('globalScope: ' + a);
block: 1
localScope: 1
[system][error]Script Failed (ReferenceError: a is not defined)

var文の省略

通常、変数はvar文を使用して明示的に宣言しますが、varを省略した変数に初期値を代入した場合も変数の宣言とみなされます。なお、varを省略して宣言した変数はどこのスコープ上から宣言しても必ずグローバル変数の扱いとなります。

//Global
a = 'global variable';

function func() {
  //Local
  b = 'global variable';
}

グローバルスコープでvarを省略して宣言した変数aはグローバル変数となります。

a = 1;
function func() {
  print('localScope: ' + a);
}
func();

print('globalScope: ' + a);
localScope: 1
globalScope: 1

関数下のローカルスコープでvarを省略して宣言した変数aはグローバル変数となります。 varを付けた場合と比較してください。varを付けた場合はグローバルスコープから参照不可でしたが、varを省略した場合は参照可となります。

function func() {
  a = 1;
  print('localScope: ' + a);
}
func();

print('globalScope: ' + a);
localScope: 1
globalScope: 1

⑤ 変数の巻き上げ (Hoisting)

JavaScriptでは、いかなる場所で変数を宣言しても、そのスコープ内の先頭で宣言されたことと同じように扱われます。これを「変数の巻き上げ(Hoisting)」と呼びます。注意として、変数の宣言は巻き上げられますが、初期化は含まれません。

次のコードを実行してみます。

はじめに関数内から変数aが参照されます。一見、変数aが宣言されておらずReferenceErrorが発生するように思いますが、結果はundefinedとなりました。これはvar a = 1が巻き上げられ、グローバルスコープの最上位でvar aが宣言されたように扱われたからです。変数の巻き上げには初期化が含まれないため、初期化されていない変数の参照結果としてundefinedとなり、その後、a = 1が処理され、1が代入されることになります。

function func() {
  print('localScope: ' + a);
}
func();

print('globalScope: ' + a);
var a = 1;
print('globalScope2: ' + a);
localScope: undefined
globalScope: undefined
globalScope2: 1

下記のコードと等価となります。

var a;

function func() {
  print('localScope: ' + a);
}
func();

print('globalScope: ' + a);
a = 1;
print('globalScope2: ' + a);

次に関数下のローカルスコープで試してみます。

var a = 1が巻き上げられ、関数下のローカルスコープの最上位でvar aが宣言されたように扱われます。変数aはローカル変数となるため、グローバルスコープからは参照できません。

function func() {
  print('localScope: ' + a);
  var a = 1;
  print('localScope2: ' + a);
}
func();

print('globalScope: ' + a);
localScope: undefined

[system][error]Script Failed (ReferenceError: a is not defined)

下記のコードと等価となります。

function func() {
  var a;
  print('localScope: ' + a);
  a = 1;
  print('localScope2: ' + a);
}
func();

print('globalScope: ' + a);

最後にvarを省略したらどうなるでしょうか。

はじめに関数内から変数aが参照されますが、ReferenceErrorが発生しました。varがついていないと変数の巻き上げ対象になりません。そのため、a = 1が処理されない限り、変数aが存在しないことになります。

function func() {
  print('localScope: ' + a);
}
func();

print('globalScope: ' + a);
a = 1;
print('globalScope2: ' + a);
[system][error]Script Failed (ReferenceError: a is not defined)

⑥ 変数の重複宣言

JavaScriptでは、同じ変数名の変数を重複宣言しても、エラーが発生しません。実際にどのような扱いになるか確認してみます。

グローバルスコープ上に同じ変数名の変数a、ローカルスコープ上に同じ変数名の変数bをそれぞれ重複宣言してみます。同スコープ内に同じ名前の変数が重複宣言された場合は、後よりの宣言に再定義されます。

var a = 1;
var a = 2;

function func() {
  var b = 3;
  var b = 4;
  print('localScope: ' + b);
}
func();

print('globalScope: ' + a);
localScope: 4
globalScope: 2

次にグローバル変数とローカル変数で同じ変数名の変数aを重複宣言した場合、どのような扱いになるでしょうか。

関数下のローカルスコープでは、同スコープ内で宣言したローカル変数aが参照されました。グローバルスコープでは、同スコープ内で宣言したグローバル変数aが参照されました。2つの変数は同じ名前ですが、スコープごとに別々の変数として扱われます。

var a = 1;

function func() {
  var a = 2;
  print('localScope: ' + a);
}
func();

print('globalScope: ' + a);
localScope: 2
globalScope: 1

下記のコードと等価となります。

var a = 1;

function func() {
  var b = 2;
  print('localScope: ' + b);
}
func();

print('globalScope: ' + a);

5. まとめ

いかがでしたか。一見単純なvar文ですが、宣言する場所、省略するか否か、変数名によって、変数の扱いが変化します。しかしながら、これらの性質をすべて理解し応用する必要はありません。むしろ限定的に使用した方がメリットがあります。例えば、下記のようなコーディング規約を適用するだけで、変数のスコープや初期値が明確になり、シンプルで理解しやすいコードとなります。シンプルなコードはバグの抑制につながりますのでルールを設けてコーディングすることをお勧めします。

コーディング規約 (例)

  • 変数の宣言は、varを付けて初期化する。
  • 変数の宣言は、使用するスコープの先頭に記載する。
  • グローバル変数で使用している変数名はローカル変数名として使用しない。
  • ローカルスコープ内でしか使わない変数はグローバル変数とせず、ローカル変数とする。
var g_val = 0;
var g_val2 = 0;

function func1() {
  var i = 0;
  var j = 0;
  :
  i = i + 1;
  :
}
function func2() {
  var i = 0;
  var j = 0;
  :
  i = i + 1;
  :
}

g_val = g_val + 1;

:

リンク

Standard ECMA-262 5.1 Edition ECMAScript® Language Specification - 12.2 Variable Statement

NEQTOとは

NEQTOハードウェア

NEQTO技術ドキュメント