こんにちは、とまだです。
JavaScript を本格的に学び始めると、スコープとクロージャという言葉をよく耳にすることがあると思います。
今回は、JavaScript における「スコープ」と「クロージャ」について、基本的な概念から実践的な使い方まで解説します。
難しい用語はできるだけ避け、日常生活の例を使って直感的に理解できるよう心がけました。
はじめに
対象読者
- JavaScript を使い始めた初心者の方
- スコープとクロージャに苦手意識がある方
- 基本を押さえてスキルアップしたい方
この記事を読んだ後に身につくスキル
- スコープの種類と違いを理解し、適切に使い分けられるようになる
- クロージャの仕組みを理解し、実践的に活用できるようになる
- よくある間違いを回避し、バグの少ないコードを書けるようになる
スコープとは?
スコープとは、変数や関数の「有効範囲」のことです。
日本語で言えば「見える範囲」とか「使える範囲」というイメージです。
例えば、家の中を想像してみてください。
リビングにある本は家族全員が見ることができますが、自分の部屋にある日記は自分しか見ることができませんよね。
これと同じように、JavaScript でも変数や関数にはスコープがあります。
グローバルスコープとローカルスコープの違い
JavaScript には「グローバルスコープ」と「ローカルスコープ」の 2 つのスコープがあります。
先ほどのリビングの例で言うと、次のようなイメージです。
- グローバルスコープ:リビングのようなもの。どこからでもアクセスできるし、見える
- ローカルスコープ:個人の部屋のようなもの。その部屋(関数)の中でしかアクセスできない
例を見てみましょう。
// グローバルスコープ
const globalVar = '私は家族全員に見える';
function myRoom() {
// ローカルスコープ
const localVar = '私は自分の部屋でしか見えない';
console.log(globalVar); // アクセス可能
console.log(localVar); // アクセス可能
}
console.log(globalVar); // アクセス可能
console.log(localVar); // スコープ外なのでアクセス不可
クロージャとは?
クロージャは、関数の中に定義された関数が、外側の関数のスコープにアクセスできる仕組みです。
ちょっと難しそうに聞こえますが、例を使って説明しましょう。
クロージャの定義
クロージャは、「関数」と「その関数が作られた環境」のセットです。
もう少しわかりやすく言うと、関数が作られた時の状態(変数や関数)を覚えている関数のことです。
例えば、こんな感じです。
function outerFunction(x) {
return function innerFunction(y) {
return x + y; // xは外側の関数の変数
};
}
const add5 = outerFunction(5);
console.log(add5(3)); // 8
この例では、innerFunction
が outerFunction
の x
にアクセスしています。
これがクロージャです。
クロージャの使いどころ
パッと見ると、クロージャを使う場面が思いつかないかもしれませんが、実はとても便利な機能です。
たとえば、次のようなことができます。
- プライベート変数の実現
- 状態を持つ関数の作成
- コールバック関数でのデータの保持
例を見てみましょう。
function createCounter() {
let count = 0;
return function () {
return ++count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
この例では、count
変数は外部からアクセスできません。
ですが、createCounter
関数が返す関数を通じて、間接的に操作しています。
言い換えると、count
変数は counter
関数の中に閉じ込められているということです。
中の関数が外側の状態を覚えているので、状態を持つ関数を作るのに便利です。
実践的なコード例で学ぶスコープとクロージャ
ここまでの知識を使って、実践的なコード例を見ていきましょう。
カウンターを作ってみよう
先ほど少し触れましたが、クロージャを使ってカウンターを作ることができます。
これは、プライベートな状態を持つオブジェクトを簡単に作れる例です。
function createCounter() {
let count = 0;
return {
increment: function () {
return ++count;
},
decrement: function () {
return --count;
},
getCount: function () {
return count;
},
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
この例では、count
変数は外部からアクセスできませんが、返されたオブジェクトのメソッドを通じて操作できます。
こうすることで、外部から状態を変更される心配がなくなります。
また、createCounter
内の詳しい実装は隠蔽されているため、外部からは見えません。
言い換えると、カプセル化されたオブジェクトを作ることができます。
カプセル化とは、オブジェクト指向プログラミングの基本的な概念の 1 つで、データとそれに関連する操作を 1 つの単位にまとめることです。
モジュールパターンを理解する
クロージャを使って、モジュールパターン
というデザインパターンを実現することもできます。
これは、関連する機能をまとめて、公開したい部分だけを外部に公開する方法です。
const calculator = (function () {
let result = 0;
function add(x) {
result += x;
}
function subtract(x) {
result -= x;
}
return {
add: add,
subtract: subtract,
getResult: function () {
return result;
},
};
})();
calculator.add(5);
calculator.subtract(2);
console.log(calculator.getResult()); // 3
この例では、result
変数は外部からアクセスできませんが、公開されたメソッドを通じて操作できます。
これにより、内部の実装を隠蔽しつつ、必要な機能だけを公開することができます。
スコープとクロージャのよくある間違い
スコープとクロージャは強力な機能ですが、使い方を間違えるとバグの原因になることがあります。
ここでは、よくある間違いとその回避方法を紹介します。
※少し高度な内容になりますが、理解しておくとコードの品質が向上します。
グローバル変数の乱用
グローバル変数は便利ですが、乱用するとコードの予測可能性が下がり、バグの原因になります。
悪い例:
var count = 0;
function incrementCount() {
count++;
}
// 他の場所でcountが変更される可能性がある
良い例:
function createCounter() {
let count = 0;
return function () {
return ++count;
};
}
const increment = createCounter();
この良い例では、count
変数はクロージャの中に隠蔽されており、予期せぬ変更から守られています。
this キーワードの誤用
this
キーワードは、使用するコンテキストによって意味が変わるため、しばしば混乱の原因となります。
悪い例:
const obj = {
value: 42,
getValue: function () {
setTimeout(function () {
console.log(this.value); // undefined
}, 1000);
},
};
obj.getValue();
良い例:
const obj = {
value: 42,
getValue: function () {
const self = this;
setTimeout(function () {
console.log(self.value); // 42
}, 1000);
},
};
obj.getValue();
この良い例では、self
変数を使って this
の参照を保持しています。
あるいは、アロー関数を使うこともできます。
const obj = {
value: 42,
getValue: function () {
setTimeout(() => {
console.log(this.value); // 42
}, 1000);
},
};
obj.getValue();
アロー関数は周囲のスコープの this
を継承するため、このようなケースで特に便利です。
まとめ
ここまでの内容をまとめると、次のようになります。
- スコープとは、変数や関数の有効範囲のこと
- グローバルスコープとローカルスコープは、どこからアクセスできるかの違い
- クロージャは、関数とその関数が作られた環境のセット
- クロージャを使うと、外側の状態を覚えた関数を作ることができる
- クロージャを使って、プライベート変数やモジュールパターンを実現できる
本格的に JavaScript を書き始めると、スコープやクロージャに関する知譵が必要になってきます。
今は完全に理解できなくても大丈夫ですが、もし困ったときにはこの記事を参考にしてみてください!