変数は、書かれた場所から外へは見えません。
function show() {
const inner = '内';
}
show();
console.log(inner);中からは外が見えます。逆は見えません。
→ ReferenceError: x is not defined
{} でも区切られる
let と const は、関数だけでなくブロックでも区切られます。
同じ名前ですが、別の変数です。中の n は外を隠しているだけで、書き換えてはいません。
外を使いたいのに隠していると、見つけにくい間違いになります。 → ReferenceError: Cannot access 'x' before initialization
関数は、作られた場所を覚えている
ここが要点です。
const fns = [];
for (var i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns.map((f) => f()));makeCounter() はもう終わっているのに、count は残っています。
返した関数が count を使っているので、消えずに保たれます。
これを閉包(クロージャ)と呼びます。
呼ぶたびに別の入れ物ができる
let count = 0;
function makeCounter() {
return () => {
count++;
return count;
};
}
const a = makeCounter();
const b = makeCounter();
console.log(a(), a());
console.log(b());b は 1 から始まります。a と b は別の count を持っています。
makeCounter() を呼ぶたびに新しい count が作られ、
返した関数がそれぞれ自分のぶんを覚えています。
何に使うか
設定を閉じ込める
prefix を毎回渡さなくて済みます。
「先に決まるもの」と「あとで来るもの」を分けられます。
外から触れない状態を持つ
count は外から直接書き換えられません。
関数を通してしか変えられないので、想定外の値になりません。
いちばん有名な罠
3つとも 3 です。 0, 1, 2 にはなりません。
var で作った i はループ全体で1つしかありません。
3つの関数は同じ i を覚えていて、ループが終わったときの値は 3 です。
let にすると直ります。
let は繰り返しごとに新しい i を作ります。
だから、それぞれの関数が別の値を覚えます。
var を使わない理由の中で、これがいちばん実害の大きいものです。
| 作られる数 | 見える範囲 | |
|---|---|---|
var |
ループ全体で1つ | 関数全体 |
let / const |
繰り返しごと | ブロックの中 |
覚えているのは「値」ではなく「変数」
関数が覚えているのは変数そのものです。作られた時点の値ではありません。
だから、あとで変えた値が見えます。
上の var の罠も、これと同じことが起きていました。
よくある間違い
閉じ込めたつもりで共有している
count が関数の外にあるので、a と b が同じものを触っています。
b が 3 から始まりました。
閉じ込めたい変数は、関数の中で宣言してください。
まとめ
- 変数は書かれた場所から外へは見えない。中からは外が見える
let/constは{}でも区切られる- 関数は作られた場所の変数を覚えている(閉包)
- 呼ぶたびに新しい入れ物ができる
varはループ全体で1つ。 中で作った関数が全部同じ値になる- 覚えているのは値ではなく変数。あとで変えた値が見える
次に学ぶ
関数を値として渡す書き方を学びます。
map() に関数を渡す形の正体、関数を返す関数、
そして「関数を受け取って新しい関数を作る」形までやります。