おはようございます。
今回は JavaScript の関数宣言とスコープに関してまとめて行きたいと思います。
スコープとは
スコープとは、実行中のコードが変数や関数を参照できる範囲のことを言います。
変数や関数が同じスコープ上に存在していれば参照することが出来ますが、異なるスコープに存在していた場合はお互いに参照することが出来ません。
このスコープにもいくつか種類があります。
スコープの種類
スコープにはグローバルスコープとローカルスコープの 2 種類が存在します。
更にローカルスコープは、関数スコープとブロックスコープに分類されます。それぞれどのようなものか解説していきます。
- グローバルスコープ
- ローカルスコープ
- 関数スコープ
- ブロックスコープ
グローバルスコープ
グローバルスコープとは、プログラム全体からアクセス可能なスコープのことです。
トップレベル(ブロック{}で囲まれていない箇所に書かれた箇所)で宣言された変数などはグローバルスコープを持ち、関数からも参照可能なものになります。
ブラウザのデベロッパーツールのコンソールから以下のコードを実際に実行してみて下さい。
// ブロックで囲まれていないトップレベルに記載された変数
var global_scope = 'global';
// トップレベルからのアクセス
console.log(global_scope); // global
// 関数内からの参照
function testFunc1() {
console.log(global_scope);
}
testFunc1(); // global
このグローバルスコープで参照できる変数は、window オブジェクトに格納されます。
window オブジェクトとは、クライアントサイド JavaScript が起動するタイミング(ブラウザが起動した時点)で自動的に生成され、あとから格納されたグローバル変数やグローバル関数にアクセス出来るようになります。
windows オブジェクトはグローバルオブジェクトとも呼ばれます。先程作成した変数も window オブジェクトから参照出来るようになっています。
コンソールから試してみると先程作成した変数が window オブジェクトの配下に格納されていることが分かります。
なお、window オブジェクトは省略可能なので通常このように記述すことはありません。
関数スコープ
関数ごとに作られるスコープのことを関数スコープと言います。
関数内で宣言された変数は、関数の内側からのみ参照可能なローカル変数となり関数の外から参照することが出来ません。
関数の引数も同じく関数スコープでしか参照出来ません。
function testFunc2(args) {
var local_scope = 'local';
console.log(local_scope); // local
console.log(args); // hello! myFunc!
}
testFunc2('hello! myFunc!'); // local
console.log(local_scope); // local_scope is not defined
console.log(args); // args is not defined
ブロックスコープ
ブロック { }
で囲まれたスコープの事をブロックスコープと呼びます。
ブロックスコープ内で宣言した let や const 変数はブロックの外から参照することが出来ません。 ただし、var で作成した変数や関数はブロックスコープの外側からでも実行する事ができます。
function testFunc3() {
for(let i = 0; i < 5 ; i++) {
console.log('let: ' + i); // 0, 1, 2, 3, 4
}
// 関数はブロックスコープの外側からでも実行出来る
outerFunc(); // hello!!
// ブロックスコープ内で宣言された let, const は参照出来ない
console.log(i); // i is not defined
}
testFunc3()
function outerFunc() {
console.log('hello!!');
こちらのコードは反復処理で使用される変数を var で宣言した場合です。
ブロックスコープ外からも参照出来てしまっている事が分かります。
function testFunc4() {
// varで宣言
for (var i = 0; i < 5; i++) {
console.log('var: ' + i); // 0, 1, 2, 3, 4
}
console.log('block outer: ' + i); // 5
}
testFunc4();
ブロックは単体でも使用可能
今までは関数やfor 文の中でブロックを使用しましたが、以下のようにブロック単体でも利用可能です。
{
const test1 = 'test1';
let test2 = 'test2';
var test3 = 'test3';
}
console.log(test1); // test1 is not defined
console.log(test2); // test2 is not defined
console.log(test3); // test3
スコープチェーン
関数やブロックはネスト(入れ子)にした記述が出来ますが、スコープもネスト出来ます。
スコープがネストしている場合は、内側のスコープから外側のスコープにある変数が参照できます。以下のコードをコンソールなどで実行して動きを確認してみてください。
{
// outer
const outer = 'outer';
{
// inner
console.log(outer); // outer
}
}
const
で宣言された変数でも内側から参照出来ていることが分かると思います。
このスコープチェーンですが、自身のスコープの中に変数がなかった場合は外側のスコープを参照しに行きます。最終的にグローバルスコープを参照し、そこで参照する変数がなかった場合にはエラーになります。
また、内側にある変数と外側にある変数が同じ変数だった場合は、内側の変数が最初に見つかるので内側の変数が出力されます。
// 内側のスコープと外側のスコープで同じ変数を宣言した場合
{
// outer
const outer = 'outer';
{
// 外側のスコープと同じ変数名を宣言
const outer = 'inner';
console.log(outer); // inner
}
}
// 参照するスコープがどこにも無い場合
{
// outer
{
// inner
// グローバルスコープにも参照する変数が無いためエラーになる
console.log(outer); // outer is not defined
}
}
// 参照する変数が最終的にグローバルスコープにあった場合
const outer = 'outer';
{
// outer
{
// inner
console.log(outer); // outer
}
}
終わりに
スコープの参照範囲や変数の宣言による挙動の違いをまとめてみましたが、思いも寄らないバグを極力少なくするために必要な知識だと改めて思いました。
こういった言語仕様は JavaScript に限らずどの言語でもしっかり把握しておく必要があるので、引き続き学習してアウトプットしていきたいと思います。