中級者になるために押さえておくべきJavaScriptの言語特性の1つ、「スコープ」とは?|翔泳社の本

中級者になるために押さえておくべきJavaScriptの言語特性の1つ、「スコープ」とは?

2022/01/20 07:00

 JavaScriptの初級者から中級者になるには、その言語特性をしっかり理解しておく必要があります。今回はCodeZineを運営する翔泳社から発売中の技術書『ステップアップJavaScript』から、言語特性の1つで「変数の有効範囲」であるスコープについて紹介します。

本記事は『ステップアップJavaScript フロントエンド開発の初級から中級へ進むために』(サークルアラウンド株式会社、佐藤正志、小笠原寛)の「STEP4 押さえておくべきJavaScriptの言語特性について」から一部を抜粋したものです。掲載にあたって編集しています。

スコープ

 スコープは多くの他の言語にも存在する「変数の有効範囲」のことです。JavaScriptは細かくスコープを扱える一方、少しの記述の抜け漏れでスコープの種類が変わる落とし穴的な挙動もあります。

 さらにステップ7ではスコープに関連する特徴としてクロージャを学びますが、そのためにも前提知識となるスコープの理解を深めておきましょう。

 JavaScriptには以下のようなスコープが存在します。

  • グローバルスコープ
  • ローカルスコープ
    • 関数スコープ
    • ブロックスコープ

 また、クラスに関係する変数もスコープに関連します。一般的には次のようなものがあります。

  • インスタンス変数
  • クラス変数(static変数)

※ES6レベルのJavaScriptには仕様が存在しないので参考にしてください。

 簡単なプログラムを書いているうちは、スコープをそれほど意識しなくても思ったようなコードが書けることも多いです。複雑なコードを書くようになると、スコープを小さくすることでバグの原因を減らしたり、見通しを良くすることを意識する必要が出てきます。下に記した順にスコープが小さくなるので、特別な意図がない場合にはなるべく右側のスコープを心掛けると良いでしょう。

グローバルスコープ > クラス変数 > インスタンス変数 > 関数スコープ > ブロックスコープ

スコープの大きさと役割

スコープが大きいならば「その変数を変更できる場所」がよりたくさんあります。「コードを書いた人の意図に反し、変数の値が予想外の場所で変更されない」ことを判断する際に、スコープは重要な働きをしています。小さいスコープなら判断の間違いが少なくなりますよね。

ブロックスコープ

 ブロックとは{}で囲まれたコード群を指します。ifやforと一緒に使っていますね。constやletで宣言された定数や変数はこのブロックスコープで扱われます(この後の説明ではまとめて「変数」と示しますがconstは正確には定数です)。

 具体的にはリスト4-1-01のような動作になります。他のスコープに比べてチェックが厳密に行われることと、「ブロックの範囲」というコードを見た時にわかりやすいスコープであることが特徴です(デモはこちら)。

4-1-01 scope.js
if(true) {
const myBlockVar1 = 'myBlockVar1-true'; // これがブロックスコープの変数です
console.log(myBlockVar1);
} else {
const myBlockVar1 = 'myBlockVar1-false'; // これがブロックスコープの変数です
console.log(myBlockVar1);
}

 上記のコードのようにif-else文の二つのブロック内にmyBlockVar1という変数をそれぞれ宣言して利用することができます。コード中に二回myBlockVar1が出ていますが、それぞれ別の変数として扱われます(ifの条件がtrueのためロジックとして意味はありませんが、ブロックの性質の理解のためのサンプルです)。

 また、ブロックは特にifやforなどと一緒に利用せずとも、プログラマが自由にロジック中に追加できます。リスト4-1-02の例を確認しましょう。ブロックがスコープの範囲なので、その外で変数を利用しようとするとエラーになります。

4-1-02 scope.js
// ブロックは意図的に書くこともできます
{
const myBlockVar2 = 'myBlockVar2'; // これがブロックスコープの変数です
console.log(myBlockVar2);
}

// console.log(myBlockVar2); // エラー:ブロックの外なので利用できません

 ブロックスコープはES6で導入された新しいスコープの概念です。なるべくこのスコープの変数を活用すると堅牢なプログラムを書くことができます。

関数スコープ

 関数の中が有効範囲になるスコープです。関数の中でvarで宣言される変数のみがこれに該当します(リスト4-1-03)。

4-1-03 scope.js
function funcScope() {
var myFuncVar1 = 'myFuncVar1'; // これが関数スコープの変数です
console.log(myFuncVar1);
}

funcScope();
// console.log(myFuncVar1); // エラー:関数の外なのでmyFuncVar1は利用できません

 古くはvarを利用するしかありませんでしたが、今後は新規でコードを書く際にはほとんど使われないでしょう。後に説明する「変数の巻き上げ」のような問題もあり、気をつけることが多いものでした。

グローバルスコープ

 最も広いスコープで、プログラムのどこからでも参照できます。

 関数に全く囲まれていない最上位の領域で変数を宣言したり、関数内でも変数を宣言する際にvarやconst、letなどを付けずに使うとグローバルスコープになります(リスト4-1-04)。

4-1-04 scope.js
// 最上位(関数等で囲まれていない)でのvarはグローバルスコープに変数を宣言します
var myGlobalVar = 'myGlobalVar';

// これもグローバルスコープに変数を宣言しています
myGlobalVar1 = 'myGlobalVar1';
function myFunction1() {

// 関数の中で初めて使いましたが、varやconstが付いていないのでグローバル変数です
myGlobalVar2 = 'myGlobalVar2';
}

console.log(myGlobalVar1);

//console.log(myGlobalVar2); // まだ宣言していないのでここで呼ぶとエラー
myFunction1(); // 関数の中でグローバル変数myGlobalVar2が宣言されます
console.log(myGlobalVar2); // ここでmyGlobalVar2は利用できます

 ブラウザにおけるグローバル変数はwindowオブジェクトのプロパティとしても操作できます。

console.log(window.myGlobalVar2);

意図していないグローバル変数

「varやconst、letの宣言を付けずに変数を利用するとグローバル変数になる」ので、付け忘れにより意図しない変数がグローバル変数になってしまう場合があり、バグの原因になることがよく知られています。

変数の巻き上げとブロックスコープ

 関数スコープで特に覚えておきたい性質として「変数の巻き上げ」という動作があります。「同じ関数内で同名の変数を複数回宣言した場合に、同一の変数として扱われる」というものです。

 varで変数宣言した場合のコードで巻き上げについて確認します(リスト4-1-05)。

4-1-05 scope.js
function funcHoisting() {
var myHoistingVar1 = 'myHoistingVar1'; ❶
console.log(myHoistingVar1);

if(true) {
var myHoistingVar1 = '変更! '; ❷
console.log(myHoistingVar1);
}

console.log(myHoistingVar1); // => "変更! " ❸
}

 ❶と❷でmyHoistingVar1という変数が宣言されていますが、どちらもfuncHoisting全体がスコープとなるので、同一の変数として扱われます。ただ、おそらくこのコードを書く人は❶と❷の変数は別のものとして考えているはずです。ところが❸では❶と同値だと思っていた値が変更されてしまっており、直感に反する動作になってしまいます。

 同様のコードでletを利用するとリスト4-1-06のような動作になり、より厳密にチェックがなされます。

4-1-06 scope.js
function blockHoisting() {
let myHoistingVar1 = 'myHoistingVar1'; ❶
console.log(myHoistingVar1);

if(true) {
let myHoistingVar1 = '変更! '; ❷
console.log(myHoistingVar1);
}

// varの時には変更されましたが、ブロック変数なので影響を受けません
console.log(myHoistingVar1); // => "myHoistingVar1" ❸

// let myHoistingVar1 = '重複'; // エラー: 同じスコープ内には同名の変数は作れません ❹
}
}

 ❶と❷の変数はそれぞれ別のものとして扱われます。❸での結果は❶で宣言した時の変数の内容のままです。さらに❹のように、同じスコープ内に同名の変数を宣言しようとするとエラーになります。今回の場合、変数の変更がされなかったのでconstを利用しても同様の結果が得られるでしょう。

 constやletを利用すると「変数の巻き上げへの気配り」をしなくて済むようになります。利用できる場所ではできる限りこちらを活用しましょう(厳密に言うと巻き上げはあるのですが、気をつけて対応する必要がなくなるのです)。

 コードの堅牢性を考えれば、以下の指針に従うとよりバグの少ない記述ができます。基本はconstを用いて、変数の指す内容が変更される場合にはletを選ぶと、varを使う機会はほとんどなくなるはずです。

const → let → var

var と変数の巻き上げ

varは古くからある変数宣言の方法ですが、巻き上げについては「気をつけなければいけないポイント」として以前から扱われてきました(特に例示したような「ブロックに囲まれあたかもスコープが違うように見える」場合に勘違いしやすいです)。後発のconstやletによる変数は、より厳密なチェックをされるなど、バグが起きにくい仕組みで実現されています。

スコープのまとめ

  • スコープは変数の有効範囲のことです。
  • JavaScriptは変数の宣言の方法により、ブロックスコープ(const/let)、関数スコープ(var)などスコープの範囲が変化します。
  • varでは変数の巻き上げがバグの原因になることもありましたが、const/letを活用するとよりバグが発生しにくいプログラムを書けます。

本書の詳しい紹介やデモについて

 本書の内容について、著者のサイトで詳しく紹介されています。また、書籍内で作成するアプリケーションのデモもダウンロードできますので、ぜひご覧ください。

著者サイトで詳しい書籍紹介を見る

ステップアップJavaScript

Amazon SEshop その他


ステップアップJavaScript
フロントエンド開発の初級から中級へ進むために

著者:佐藤正志、小笠原寛、サークルアラウンド株式会社
発売日:2022年1月14日(金)
定価:2,640円(本体2,400円+税10%)

本書について

「入門者向けの本は一冊読み終わったけど、もっと良いコードを書きたい」「バグの出にくいコードの書き方を知りたい」という開発者のために、JavaScriptで特につまずきやすい部分を丁寧に解説します。