Ne:Code道場~JavaScript 関数と変数のスコープの巻~



Ne:Code道場~JavaScript 関数と変数のスコープの巻~

プログラミング学習で、漫画など視覚的なものと併用すると楽しく継続的に学んでゆける人も多いと思います。そこでこの連載では、猫の漫画を交えつつ、JavaScriptの関数と、関数に絡めての変数のスコープの学習ポイントについて紹介します。

目次
  1. はじめに
  2. 変数のスコープって例えばどういうものニャ?
  3. グローバルスコープとローカルスコープはどう違うニャ?
  4. 同じ名前なのに別物、なんだか不思議ニャ
  5. ブロックスコープについて教えてニャ
  6. 変数の巻き上げについて教えてニャ
  7. 改めてブロックスコープについて教えてニャ
  8. 最後に

はじめに

image この記事ではJavaScriptの関数と、関数の知識を踏まえての変数のスコープについて、概要について取り上げつつ、躓きポイント を補足しています。 各節ごとに、Ne:Code道場(猫de道場)の弟子猫がポイントをまとめ、最後に師匠猫がそれらを総括して3ポイントでまとめています。 それらのポイントを中心に、関数と絡めつつ変数のスコープについて理解を深めてゆきましょう。

変数のスコープって例えばどういうものニャ?

変数のスコープに関しては、以前繰り返し処理についてまとめた記事で、少しだけとりあげました。変数という値を入れる箱には、利用できる範囲というものが定められており、そのことを指して変数のスコープといいます。
例えば、遊園地における、全体利用券(フリーパス)、エリア利用券、一回券等のイメージで説明してみましょう。
全体利用券では、遊園地内のどこでも利用することができますね。JavaScriptの変数のスコープで言えば、グローバルスコープがそれにあたります。
次に、エリア利用券は、遊園地内の特定のエリアでのみ利用できますね。JavaScriptの変数のスコープで言えば、ローカルスコープがそれにあたります。
最後に、一回券は、乗り物一回ごとに消費される券ですね。これはES2015以降で利用可能になった宣言子(let)を用いることで実現できる、ブロックスコープがそちらにあたります。
では、グローバルスコープ、ローカルスコープ、ブロックスコープをそれぞれ見てゆきましょう。
image

グローバルスコープとローカルスコープはどう違うニャ?

関数の宣言・実行、そして関数の引数・戻り値と、関数記事の延長で取り上げたこの変数のスコープ、もちろん、関数が大きく関わってきます。
関数の外で宣言されている変数はグローバルスコープになり、遊園地の全体利用券のようにどこでも利用できる変数になります。
それに対して、関数の中で宣言した変数はローカルスコープになり、遊園地のエリア利用券のように限定された範囲、つまり、その宣言された関数の中でのみ利用できる変数になるのです。
まずはグローバルスコープの変数の動きから、サンプルコードを見つつ説明します。

// グローバルスコープの変数を宣言したニャ
// ポイントを管理するようの変数だニャ
// スタートポイントとして、10ポイント用意したニャ
let point = 10;
 
// ポイントを増やす関数ニャ
function increasePoint() {
  // グローバルスコープのpoint変数を1増やしているニャ
  point++;  
}
 
// ポイントを減らす関数ニャ
function decreasePoint() {
  // グローバルスコープのpoint変数を1減らしているニャ
  point--;  
}
 
increasePoint();
increasePoint();
decreasePoint();
// ポイントを増やして×2、減らしたニャ!
// 結果はもちろん、「11」ニャ
console.log(point);

異なる関数(increasePoint、decreasePoint)で、pointという共通の変数の値を変更しているのが確認できますね。
ローカルスコープの場合は、そうはいきません。具体例をサンプルコードで見てみましょう。

// お試し用ということで、特に意味のない名前の関数ニャ
function testA() {
  // ローカルスコープの変数xを宣言しているニャ
  let x = 0;  
}
 
// そんな変数存在していないって、怒られたニャ!(T_T)
console.log(x);

ローカルスコープの変数が利用できるのは、その関数の中だけなので、関数の外ではその変数が宣言されていることを認識できないのですね。
では、次のサンプルコードを見てみましょう。

// グローバルスコープとしてxを宣言したニャ
let x = 10;
function testA() {
  // 同じ名前の変数だけれど、宣言時にエラーで怒られることは無いニャ
  // JavaScriptにとっては全く別物だと認識されている証拠ニャ
  let x = 0; 
  console.log(x);
}
 
// ここで出力されるのは、0ではなく、10だニャ
console.log(x);
// ここで出力されるのは、0ニャ!
testA();

引数もローカルスコープになり、関数の中だけ利用されます。そのサンプルコードがこちらです。

// グローバルスコープとしてyを宣言したニャ
let y = 1;
// 引数にグローバルスコープの変数と同じ名前をつけているニャ
function testB(y) {
  console.log(y); 
}
 
// ここで出力されるのは、1ニャ!
console.log(y);
// ここで出力されるのは、1ではなく、100ニャ!
testA(100);

以前記載した変数の記事で、letという宣言子を用いて同じ名前の変数を宣言できないようになっている(宣言時にエラーになる)という、ありがたい挙動について取り上げました。
スコープを学んだ現在は、上記の挙動が実際は、「同じスコープで、且つ、同じ名前は宣言できない」ということがわかります。
スコープが異なると、同じ名前の変数が、JavaScriptにとって全く別物だと認識してくれるということですね。
image

同じ名前なのに別物、なんだか不思議ニャ

変数の記事でも取り上げましたが、変数の名前を考えることはとても重要です。でも同じ意味合いの変数であれば、同じ名前をつけたいところではありますよね。
そんな時に、スコープが違えば、同じ名前でも全く別物だと認識してもらえる挙動は、そんな名前付けの大変さをぐっと和らげてくれます。
ただ、慣れないうちは、同じ名前であることでかえって混乱してしまうかもしれません。
なので、その変数のスコープがどうなっているのかを意識しながらコードを確認すると良いでしょう。
その変数の宣言されているところはどこなのか、関数内部の場合は引数であるのか、無いのか等をこまめに確認してみると良いですね。
もしも、グローバルスコープの変数と、ローカルスコープの変数が同じ名前であることによって、かえってわかりにくくなってしまうと感じた場合は、慣れるまでは意図的に名前を変えてみるのもありです。
image

ブロックスコープについて教えてニャ

varという宣言子を用いていた場合、その変数にはブロックスコープにはなりえませんでした。というのも、「変数の巻き上げ」というJavaScript固有の動きが内部的に発生していたからです。
varで宣言した変数は、実行時に、グローバルスコープ、ローカルスコープのいずれかの先頭まで巻き上がるという特徴があります。
image

変数の巻き上げについて教えてニャ

if (true) {
  var test = 10;
}
// 結果が10って出力されるニャ?!
console.log(test );

上記のコードは、実行時に以下のようになっています。

// 変数の宣言がグローバルスコープの先頭に移動しているニャ!
var test;
if (true) {
  // グローバルスコープとして宣言したものに値を代入していることになっているニャ。。
  test = 10;
}
// これは確かに「10」って出力されるはずニャ!
console.log(test );

if文の中で宣言しているものをif文の下で利用したり等のコードは、少し直感的ではないですよね。少なくとも他の多くのプログラミング言語とはずいぶんと異なる挙動になります。
巻き上げは、グローバルスコープ・ローカルスコープ単位で行われるので、関数内に宣言された場合は以下のようになります。

// 先程のコードをfuncAという適当関数に入れただけニャ
function funcA() {
  if (true) {
    var test = 10;
  }
  // ここでもやっぱり、結果が10って出力されるニャ?!
  console.log(test );
}
funcA();

上記のコード、実行時の変数巻き上げを視覚化してみると以下のようになります。

function funcA() {
  // この変数はローカルスコープなので、所属している関数の先頭まで巻き上がっているニャ!
  var test;
  if (true) {
    test = 10;
  }
  console.log(test );
}
funcA();

image

改めてブロックスコープについて教えてニャ

let宣言子で宣言した場合は、巻き上げられることはありません。宣言したところで、変数は誕生します。そしてその宣言したブロック(波括弧)内だけで利用可能というのが特徴です。
波括弧は、if文やfor文などでも書いていましたね。ブロックスコープを用いることで、変数のスコープを関数よりももっと小さい範囲に抑えることができるというのが特徴です。
スコープが広いと、いろんなところから変更できますから、予期せぬ挙動が埋め込まれてしまう可能性があります。
なので、本当にグローバルスコープにしなければいけない変数以外は、可能な限りローカルスコープ、また、一歩進んでブロックスコープというように、スコープを小さくできないかという観点から書き上げたコードを見直してみるのも良いでしょう。
image

最後に

関数に絡めての変数のスコープについて、猫弟子がいくつか上げたポイントを、猫師匠が厳選3ポイントでまとめたのがこちらです。

  1. 変数にぶらさがる知識としてのスコープ、とても重要ニャ
    1. 変数には利用できる範囲があり、それはスコープというニャ
    2. まずは、関数の内か、外かで大きくスコープが別れてくるというのがポイントニャ
  2. スコープが違えば、同じ名前であっても全く別物だとJavaScriptは認識するニャ
    1. スコープが違えば同じ意味合いのものに同じ名前を使いまわしできるのは、名前を考えるコストがぐっと軽くなるニャ
  3. letという宣言子を用いることで変数の巻き上げは起きないニャ
    1. varで宣言した変数は、変数の巻き上げによって、グローバルスコープ、もしくはローカルスコープの先頭まで巻き上がるという特徴は覚えておいて損は無いニャ

今回は、関数三回目ということで、関数に絡めての変数のスコープを取り上げました。
何気なく使っていた変数には、利用範囲があり、今回学んだ関数がその大きな範囲区切りになることを意識しておくと、関数を使いこなすことにもつながってゆくと思います。
関数を使いこなすことによって、楽で読みやすいコードを作ってゆくことができます。するとコードへの理解も深まり、プログラミングが更に楽しくなってくるでしょう。
関数の引数・戻り値への理解をしっかり身につけつつ、今後の学習に繋げてゆきましょう。
image

小泉七菜
この記事を書いた人
小泉七菜
\ 無料体験開催中!/自分のペースで確実に習得!
オンライン・プログラミングレッスンNo.1のCodeCamp