スコープについて
プログラミングでのスコープとは、ある変数や関数が特定の名前で参照される範囲のこと。ある範囲の外に置いた変>数等は、通常、その名前だけでは参照できない。このときこれらの変数はスコープ外である、「見えない」といわれる。
変数のアクセス権や生存期間といった話になります。
スコープの種類
- グローバルスコープ
- ローカルスコープ
- 関数スコープ
- ブロックススコープ
グローバルスコープ
プログラムのトップレベルで宣言された変数は、グローバル変数となり、プログラム全体のどこからでもアクセス可能なグローバルスコープを持ちます。
var scope = 'global'
// トップレベルからのアクセス
console.log(scope);
// 関数内からのアクセス
(function () {
console.log(scope);
})();
出力結果
ローカルスコープ
グローバル変数以外のすべての変数は。ローカルスコープを持つローカル変数です。
- 関数スコープと
- ブロックススコープの2つに分けることができます。
関数スコープ
関数(function
)ごとに作られるスコープのことを関数スコープといいます。
関数スコープ内で,var
,let
,const
のいずれかで変数宣言をすると、関数の外部からはアクセスできず、関数の内側からのみ利用可能なローカル変数になります。また、関数の仮引数も同じく関数スコープを持ちます。
function fn(arg) {
// fn関数の関数スコープを持つローカル変数を定義
var scope = 'local';
// fn関数のスコープ内のためのアクセス可能
console.log(scope); // -> local
console.log(arg); // -> argument
}
fn('argument');
// 関数スコープの外側からはローカル変数や仮引数にアクセスできない
console.log(scope); // -> ReferenceError
console.log(arg); // -> ReferenceError
出力結果
ブロックススコープ
ブロック({}
)ごとに作られるスコープのことをブロックススコープという。
ブロックススコープ内でlet
またはconst
を用いて宣言した変数はブロックの外側からはアクセスできずブロックの内側からのみアクセス可能なローカル変数になります。
function fn() {
// fot文のブロックススコープを持つローカル変数 i を定義
for (let i = 0; i < 3; i++) {
console.log(i); // -> 0,1,2
}
// for文のブロックススコープの外側からはアクセスできない
console.log(i); // -> ReferenceError
}
fn();
出力結果
ES2015からのローカルスコープ ( メモ )
JavaScriptには当初、グローバルスコープと関数スコープしか存在しなかったため、「関数スコープ=ローカルスコープ」という図式が成り立っており、そのように解説している媒体がほとんどです。
しかし、ES2015でブロックススコープが追加されたことにより、ローカルスコープには関数スコープとブロックススコープの二種類が存在するようになりました。
変数宣言の巻き上げ(ホイスティング)
関数スコープの中で変数宣言を行うと、変数は関数の先頭に引き上げられます。このことを巻き上げやホイスティングと言います。
これがどういうことなのか、具体例と一緒に解説していきます。
次のコードではscope
変数を宣言するより前に、console.log
でscope
変数を参照しようとしています。
function fn() {
console.log(scope);
var scope = 'local';
}
fn();
変数宣言より前に参照しようとしているのですから、エラーになってしまいそうです。しかし、実際にはundefined
が返されます。
その理由はスコープの途中で宣言したローカル変数の宣言部分が、スコープの先頭に巻き上げられたからです。
先ほどのコードが次のように変わったとするとイメージがしやすいです。
funtion fn() {
// 変数宣言だけ先頭に巻き上げられる
var scope;
console.log(scope); // > undefined
// 値の代入は元の場所で行われる
scope = 'local';
}
fn();
巻き上げられるのは宣言だけであり、値の代入自体は元の場所で行われるため、console.log
を行った時点では、scope
はundefined
を返します。
なおlet
とconst
の場合はブロックの先頭に巻き上げはされるのですが、宣言より前でアクセスしようとすると、ReferenceErrorとなってしまう。
このアクセスできない領域を temporal dead zoneと言います。
コード①
// ES5
function driversLicence5(passedTest){
if(passedTest) {
var firstName = 'Jphn';
var yearOfBirth = 1990;
console.log(firstName + '
born in ' + yeraOfBirth + ',
is now officially allowed to
driver a car.');
}
}
driversLicence5(true);
// ES6
function driversLicence6(passedTest){
if(passedTest) {
let firstName = 'Jphn';
const yearOfBirth = 1990;
console.log(firstName + '
born in ' + yeraOfBirth + ',
is now officially allowed to
driver a car.');
}
}
driversLicence6(true);
正常に機能する
コード②
// ES5
function driversLicence5(passedTest) {
if (passedTest) {
var firstName = 'John';
var yearOfBirth = 1990;
}
# 移動
console.log(firstName + ',born in ' + yearOfBirth +
',is now officially allowed to drive a car.');
}
driversLicence5(true);
// ES6
function driversLicence6(passedTest) {
if (passedTest) {
let firstName = 'John';
const yearOfBirth = 1990;
}
# 移動
console.log(firstName + ',born in ' + yearOfBirth +
',is now officially allowed to drive a car.');
}
driversLicence6(true);
if文のスコープ外にconsole文を移動させた。
ES6の方ではfirstName
が定義されていないというエラーが表示された
script.js:51 Uncaught ReferenceError: firstName is not defined
これは何故なのか?
let
とconst
はローカルスコープの中の関数スコープではなくブロックスコープだからです。
- ローカルスコープ
- 関数スコープ
- ブロックススコープ ○
let
,const
宣言だと、if
ブロックの中で宣言した変数・定数を、ブロックの外で使用することはできません。
コード③ ( letとconstの違い )
function driversLicence6(passedTest) {
let firstName;
const yearOfBirth = 1990;
if (passedTest) {
firstName = 'John';
}
console.log(firstName + ',born in ' + yearOfBirth +
',is now officially allowed to drive a car.');
}
driversLicence6(true);
const
はif
文のブロック外に定義した値に代入する値も定義する必要がある。
let
の場合は定義する値だけで値の代入はブロック内で定義しても大丈夫という認識です。
まとめ
関数スコープ
- functionの中に書かれた
- functionの中に書かれた変数・関数はその内部でしか変更・参照はできない 外部から参照できないので変更に強いモジュールが作れる
var
- 巻き上げが起こる
- JavaScriptは関数内のどこでもvarの宣言を書ける
- 関数のどこで宣言しても、先頭で定義したものとしてみなされる
- 先頭部分で使いたい変数は全て定義するのが定石
- 関数スコープである
- 関数内ならどこからでも参照できる
var pokemon = 'ライチュウ';
function sing(){
//JavaScriptは関数内のどこでもvarの宣言を書ける
//これらの変数は関数のどこで定義しても、先頭で定義したものとして見なされる
//var pokemon;
console.log(pokemon);
var pokemon = 'ピカチュウ';
console.log(pokemon);
}
sing();
let
- ブロックススコープ(関数スコープとしても使える)
- 今までvarで書いてきた所はletで置き換えられる
- if,for等の{}内のブロックのみで使うブロックスコープが作れるので、スコープを狭く出来るため影響範囲を狭められる
- 変数の巻き上げは従来どおり起きる
if (true) {
//if内はスコープが無いのでグローバルスコープ扱い
var test = 'hoge';
}
//ブロックスコープではないので、アクセスできる
console.log(test);
if (true) {
//ブロックスコープになる
let test2 = 'hoge2';
}
//アクセスできない ( 今回のコード②と同じ現象 )
console.log(test2);
const
- 再代入不可能な変数を作る(つまり定数にできる)
- 再代入しようとするとエラーになる
- letと同じブロックスコープ(関数スコープとしても使える)
- 巻き上げは起きる
- ほとんどは再代入は不要なのでconstを使う
- constを使っておけば値が変わることが無いので、値が変わるかも?という懸念が無くてコードが読みやすい
- forのイテレータのような再代入が必要な所のみletを使う
const test3 = 'hoge';
test3 = 'fuga'; //エラー
console.log(test3);