今回はJavaScriptのスコープとクロージャについての記事です。
私自身、JavaScriptをさわり始めた頃はなんとなくでコードを書いたり、意味も分からずコピペしたりだったため、
ここどういうこと?
自分でもわかりません
というやりとりが何回もありました。今でもありますが・・・。
しかし以前よりは、JavaScriptを勉強し始めて少し経ち、この二つを理解してからよりコードの理解が深まったと思ったためご紹介させていただきます。
スコープ
スコープとは
スコープとは、MDN Web Docsでは
実行の現在のコンテキスト。値 と式が「見える」、または参照できる文脈。
と定義されていました。
言い換えると、「変数の有効範囲」のことです。
スコープは、大きく分けてプログラムのどこからでも参照できるグローバルスコープと部分的な範囲のみ参照できるローカルスコープがあります。
グローバルスコープで定義された変数はどこからでも参照することができるため便利なように思えますが、コードが複雑になってくると管理できなくなり予期せぬエラーの原因になります。
スコープが狭いほどリスクを減らし、参照している処理も調べやすくなるため、なるべくローカルスコープでの定義を心がけましょう。
ローカルスコープ
ローカルスコープには以下のようなスコープがあります。
- ブロックスコープ
- 関数スコープ
ブロックスコープ
{と}で囲んだ範囲をブロックと呼び、ブロックによるスコープのことをブロックスコープと呼びます。(引用:JavaScript Primer)
関数スコープ
関数によるスコープのことを関数スコープと呼びます。(引用:JavaScript Primer)
constやletで宣言された変数はブロックスコープ、varで宣言された変数は関数スコープをとります。そのためブロックスコープ内で変数をvarで定義してしまうとブロックの外からでも参照できてしまいます。
コードで確認してみます。
a = 1;
//varの場合
if(true){
var a = 0;
}
console.log(a);
//letの場合
if(true){
let a = 0;
}
console.log(a);
varで定義した場合かletで定義した場合の違いについてみてみましょう。
出力結果は、
varの場合は0
letの場合は1
となりました。
出力結果が変わってしまった原因として、ブロックスコープか関数スコープかという点があげられます。
varの場合は関数スコープを取るため、条件式の外からでも参照可能となりますが、
letの場合はブロックスコープとなるため条件式の外からは参照できません。
このように、varは予期せぬエラーの原因になってしまうことがあるため注意です。
全体を図で表すとこのような感じです。
クロージャ
クロージャを理解する前に
クロージャを理解するにあたって、JavaScriptの押さえておきたい仕様について確認します。
- 関数の中に関数を書くことができる
- 関数は値として扱え、変数に定義できる
クロージャってなに?
クロージャとは、MDN Web Docsによると下記の通りです。
組み合わされた(囲まれた)関数と、その周囲の状態(レキシカル環境)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。
言葉ではわかりにくいと思うため、コードで説明していきます。
コードで説明
数を1ずつ増やす関数を作っていきます。
- ※//〇〇は出力結果です
let num = 0;
function increment(){
num += 1;
console.log(num);
}
increment();//1
increment();//2
increment();//3
上記の場合、変数numはグローバル変数となりどこからでも参照可能となってしまいます。
そのため、下記のようなことが起こってしまう可能性が出てきます。
let num = 0;
function increment(){
num += 1;
console.log(num);
}
num = 100;
increment();//101
increment();//102
increment();//103
このようなことを防ぐためにクロージャの出番です。
先程のコードをクロージャを用いて書き直すと以下のようになります。
function countUp(){
let num = 0;
function increment(){
num+=1;
console.log(num);
}
return increment;
}
const counter = countUp();
counter();//1
counter();//2
counter();//3
上記の場合、(内側の関数)関数incrementから(外側の関数)関数countUpへの参照を可能にしているため、関数incrementはクロージャの役割をしています。
また、変数numが関数countUpの中で定義されたことで、スコープが狭くなり外部から参照できなくなりました。
クロージャをうまく使うことで予期せぬエラーを防ぎ、よりスマートなコードを書くことができます。
他にもクロージャを用いることによってできることが記載されているため、詳しく知りたい方はこちらをご覧ください。
まとめ
今回は、JavaScriptのスコープ、クロージャについてご紹介しました。
本記事を読んで、今までわからなかったコードが理解できた!もっと深く知りたい!と思ってくれた方が少しでもいらっしゃると嬉しいです。
次回の記事ではJavaScriptの関数についてご紹介したいと思います!
最後まで読んでくださり、ありがとうございました。