JavaScirptのスコープって何なの?いまいちよくわからないんだけど。
本日は
"JavaScirptのスコープについて解説します"
というテーマで記事を書いていきます。
この記事を書いている人
そもそもスコープとは?
スコープ=scopeとは、日本語で「可視範囲」という意味で、ものが見える範囲のことを指します。
そしてプログラミングではこのスコープは変数や定数、関数の影響する範囲のことを指します。
プログラミングにおいて宣言した変数や定数はどこでも使えるわけではありません。
そもそも変数って何?
関数って何なの?引数って何?
という方は以下の記事をご覧ください。
変数が有効な範囲の中では変数を使うことができますが、有効な範囲外ではエラーとなってしまいます。
この有効な範囲というのがスコープです。
例えば以下のような例です。
{
let abc =10;
//10が出力されます
console.log(abc);
{
//10が出力されます
console.log(abc);
}
}
上記の場合、結果は10,10となります。
{
{
let cde =20;
}
//スコープ外のためエラーになります
console.log(cde);
}
上記の場合は、cdeは別のブロックスコープ内で宣言されているため、console.log(cde)で出力しようとしてもエラーとなってしまいます。
出力しようとしても、
cdeっていう変数が見つからないよ。エラーで返しちゃお
となっているということです。
スコープの種類
スコープには大きく分けて次の3種類あります。
スコープの種類
・グローバルスコープ
・ブロックスコープ
・関数スコープ
ただ同じようなものが3種類と覚えるのではなく、グローバルスコープがあり、別途ローカルスコープがあった上でローカルスコープの下にブロックスコープと関数スコープがあると覚えましょう。
ローカルスコープはブロックスコープと関数スコープの総称です。
繰り返しとなりますが、スコープは変数や定数が使える範囲の事です。
グローバルスコープとは
プログラムのトップレベルで宣言された変数はグローバル変数となり、1つのプロジェクトの全ての場所から呼び出す事が出来るようになります。
このように、プログラムのどこからでもアクセス出来る領域のことをグローバルスコープと呼ぶのですね。
グローバルとは
世界規模である様
by Google翻訳
との事ですが、まさに一つのプロジェクト全体という広い範囲で適用出来るからこそこのような名前になったのではないかと思います。
ブロックスコープと関数スコープ
そして反対に先程記載したブロックスコープと関数スコープはローカルスコープです。
ローカルとは
全国的なものではなく地方的なもの
by Google翻訳
という意味で、限定的な領域、つまりは限定的なスコープということになります。
そしてローカルスコープはブロックスコープと関数スコープと呼ばれるものに分けられます。
ブロックスコープは、if文などで出てくる{}波括弧で囲まれた部分です。
if(条件式){
//この中がスコープ
}
ブロックスコープはif文だけではなく、for文にも当てはまります。
関数スコープは以下のような形です。
function(){
//この中が関数スコープ
}
なぜスコープがあるのか
なぜスコープが存在するのか、その理由は「変数名が競合することを避けるため」です。
JavaScriptでは同じ変数名を同じプロジェクト内で宣言することがあります。そういった場合にスコープがあることによって、変数が使えるエリアを限定することができます。
変数を限定することで、適切に変数を使うことができるわけですね。
例えば、とあるところで
let oneDay= 24;
という変数を宣言したとしても別のところで
let oneDay =23;
と書き換えられたら困りますよね。
変数は適切な場所で適切な範囲で使えてこそ意味があるからです。
そういう意味では「変数を守る」、という表現をしてもよいかもしれません。
実際、スコープは変数の保守性を高める役割があると言われています。
このように、スコープがあることによって、変数を適切な範囲で使うことができるのです。
なぜ関数スコープとブロックスコープがあるのか
ではなぜ関数スコープとブロックスコープがあるのでしょうか。
1つ目の理由は、varという変数宣言が関係しています。
varは古い変数宣言ですが、ES6以降で使われているletとは少し違う点があります。
varの特徴
・スコープの有効範囲が関数スコープ
・変数の再宣言可能
・巻き上げ時にエラーにならない(undefinedになる)
varで宣言した変数は関数スコープのため、ブロックスコープを無視して他の変数宣言と競合する可能性があります。
varはスコープが広く再宣言を行えます。そのため、気づかないうちに同じ変数名を再宣言してしまい、エラーの温床となる場合があります。
ただ、結論をお伝えすると現在ではvarは殆ど使われておらず、関数スコープを気にする必要はありません。constやletといったブロックスコープの変数を使うのがベストです。
letはブロックスコープが適用されるため、適用される範囲が狭く、バグが起きにくいためletの使用が推奨されています。
letで宣言した変数は、宣言されたブロックスコープや関数スコープのあいだで有効な変数となります。
しかし、以下のようにスコープから外れると使えなくなります。
if(条件式){
let foo;
//fooという変数は有効(スコープ内のため)
if(条件式){
//fooという変数は有効(スコープ内のため)
}
//fooという変数は有効(スコープ内のため)
}
//fooという変数は無効(スコープ外のため)
2つ目の理由はthisというJavaScirptではじめから用意されている特別な変数が関係しています。
このthisは関数スコープ単位となっているため、ブロックスコープと関数スコープで分けていることになります。
「変数の巻き上げ」とは
変数の巻き上げとは、varを使った時に起きる現象です。以下のコードをご覧ください。
var hero = 'アンパンマン';
function fight() {
var hero;
console.log(hero);
hero = "ウルトラマン";
console.log(hero);
}
fight();
こちらの出力結果はどうなるでしょうか。
アンパンマン
ウルトラマン
でしょうか。
実際には変数の巻き上げが関係しており、結果は異なります。
結果は
undefined
ウルトラマン
となります。
どういうことか見てみましょう。
もうひとつ、JavaScript の変数にまつわる独特な点として、例外を発生させることなく後に宣言した変数を参照できる点が挙げられます。
この考え方は巻き上げとして知られています。JavaScript の変数は関数や文の先頭まで、ある意味「巻き上げられ」、持ち上げられます。一方、巻き上げられない変数は undefined 値を返します。
そのため、その変数を使用したり参照した後に宣言や初期化を行っても、依然として undefined が返されます。
とのことですが長ったらしくてよくわからないですよね。←
要は、
関数の中で宣言した変数に関しては全て関数内の一番最初に持ち上げる(巻き上げる)
ということです。
先ほどの例で言うと以下のようになります。
var hero = 'アンパンマン';
function fight() {
var hero; //var heroは巻き上げられてundefinedになる
console.log(hero); undefinedを出力してしまう
hero = "ウルトラマン";
console.log(hero);
}
fight();
JavaScriptでは、関数内のどこにでもvar文を使用して変数を宣言することができてしまいます。
そして、これらの変数は関数内のいかなる場所で宣言されたとしても、その関数の先頭で宣言されたのと同じように動作するのです。
対策は単純です。varを使わなければ大丈夫です。
現在は殆どletかconstを使っているので、こういったケースに遭遇することは少ないかもしれませんが、昔に作ったサイトの改修などでvarの知識が必要になることがありますので、覚えておいた方が良いかもしれません。
まとめ
はい、いかがでしたでしょうか。
スコープとは可視領域、つまり変数が使える範囲のことで、
・プログラム全体で使えるグローバルスコープ
・局所的に使えるブロックスコープと関数スコープ(ローカルスコープ)
の3つがありました。
スコープはプログラムを書いていると必ず意識するポイントですし、知らないと思わぬエラーに遭遇する可能性がありますので是非押さえておきましょう。
それではまた明日の記事でお会いしましょう✨
おしまい✨