#はじめに
【対象】
・クロージャーについて何度も調べて、実践しているが、なかなか理解できない人
・グローバル変数とローカル変数の違いはわかる人
・グローバル変数を出来るだけ使わないようにするのは分かるけど、やり方が分からない人
#そもそも、クロージャーとは?
クロージャは、関数と、その関数が宣言されたレキシカル環境の組み合わせです。
MDNより
要は、
**「関数を囲むスコープにある変数を参照できる関数」**の事。
#クロージャーの特徴
①関数が呼ばれるたびに結果が変わる事
②グローバル変数を減らすことで、他のプログラムからの干渉を受けずらく出来る
③処理回数が減らすことが出来る(※重要)
④レキシカルスコープの言語で使う
クロージャーの特徴について説明する前に。
まずは、レキシカルスコープとダイナミックスコープの違いからの説明です。
(分かる人は飛ばしてください。)
#・レキシカルスコープとダイナミックスコープ
####①レキシカルスコープ
→関数を定義した時点でスコープが決まる。
↓コードで説明
var x = 10;
function A(){
console.log(x);
}
function B(){
var x = 1000;
A();
}
A(); // 10
B(); // 10
function A(){}
として関数定義した時点で参照していたxが代入されている。
・メジャーな言語(JSなど)で採用される。
####②ダイナミックスコープ(Perl等の言語)
実行時>定義時
→関数定義時にもスコープは決まるが、実行時のスコープ優先。
var x = 10;
function A(){
console.log(x);
}
function B(){
var x = 1000;
A();
}
A(); // 10
B(); // 1000
#クロージャーについて1からの説明
先程、記載した通り、
クロージャーとは、
関数を囲むスコープにある変数を参照できる関数の事です。
コードで説明すると、
function outer() {
let place = '大阪';
console.log(place);
function inner() {
alert(place);
}
return inner;
}
let Func = outer();
Func();
上から順に説明していきます。
(基本的な所まですべて記述します)
・let place
:outer(){}
のローカル変数。(※outer()
実行時以外は参照できない)
・console.log(place);
:outer();
で実行される
・function inner() {}
:これが、クロージャーです。
・alert(place);
:Func();
で実行される。
・let Func = outer();
:ここで、outer();
が実行され、また、inner()
がクロージャーとなって、代入されている。
ここで分かった、クロージャーの性質は、
outer()が実行されていないのに、outer()内の変数placeが参照される事です。
###【上記コード:ここまでの流れのまとめ】
let Func = outer();
とFunc();
で関数の実行を行っている
↓
let Func = outer();
の定義でのみ、outer()
が実行され、console.log(place)
が実行される
↓
let Func = outer();
の定義でinner()
がクロージャーとして代入される
↓
Func();
でinner()
の中身を実行
#【特徴①】関数が呼ばれるたびに結果が変わる事について
~【今回行う事】~
関数を呼び出すたびに、数が増えていく処理を行います。
通常の関数では?
####【通常】
function outer() {
let count = 1;
count++;
console.log(count);
}
outer(); // 2
outer(); // 2
outer(); // 2
上記のコードでは、outer();
を何度実行しても呼び出されるたびにcountには1
が代入されるため同じ結果しか得れません。
####【クロージャー使用】
function outer() {
let count = 1;
console.log(count);
function inner() {
count++;
console.log(count);
}
return inner;
}
let sum = outer(); // 1
sum(); // 2
sum(); // 3
sum(); // 4
実現できました。
#【特徴②】グローバル変数を減らすことで、他のプログラムからの干渉を受けずらく出来るについて
【特徴①】のコードと数字の増えるプログラムで説明すると、
下のようなコードでも動くのでは?
let count = 1;
console.log(count);
function outer() {
count++;
console.log(count); // 1
}
outer(); // 2
outer(); // 3
outer(); // 4
let count = 1;
を関数の外で定義しています。
グローバル変数として定義した場合も、思い通りの処理が完成しました!
しかし、
これでは、クロージャーを勉強している意味はありません。
そもそも、クロージャーを使う目的の一つとして
グローバル変数を使わないようにすることで、他のプログラムからの干渉を受けずらくという事があげられます。
グローバル変数を使うことで、変数名が被った際に参照先の違いで思わぬエラーが発生する可能性もあります。
上記のコードでは、グローバル変数count
にアクセスしています
それをしないために、クロージャーを使ってカプセル化を行います。
####【クロージャー使用】
function outer() {
let count = 1;
console.log(count);
function inner() {
count++;
console.log(count);
}
return inner;
}
let sum = outer(); // 1
sum(); // 2
sum(); // 3
sum(); // 4
何となくメリットが分かってきたような??
#【特徴③】処理回数を減らす
→【特徴】の部分でも記載した通り、クロージャーを使うことで、処理回数を減らすことにもつながります。
「クロージャーについて1からの説明」にあるコードを少し変えたのコードで説明します。
####【クロージャーなし】
function outer() {
let place = '大阪';
let word;
// console.log(place);
if(place === '大阪') {
word = 'せやな!';
} else {
word = 'ちゃうやん';
}
window.alert(word)
}
outer();
上記のコードだと、outer();
が呼び出されるたびに毎度ifで処理しないといけません。
しかし、、、
####【クロージャーを使うと?】
function outer() {
let place = '大阪';
let word;
// console.log(place);
if(place === '大阪') {
word = 'せやな!';
} else {
word = 'ちゃうやん';
}
function inner() {
window.alert(word)
}
return inner;
}
let Func = outer();
Func();
let Func = outer();
で一度if分の条件分岐の処理を行った後は、Func();
を何度実行しても呼び出されるのは、inner()
内部だけです。
見事に、クロージャーの使用で処理回数を減らすことを実現できました!
#【おまけ】これは無理なん?と思ったもの
##①returnではなく、関数定義内で関数実行
function outer() {
let count = 1;
console.log(count); // 1
function inner() {
count++;
console.log(count);
}
inner(); // 2
}
outer(); // 1 2
outer(); // 1 2
1
2
1
2
上記のコードでは、outer()
実行時にouter()
内でinner()
が実行されてしまっています。
関数を実行するたびにcount = 1
にcount++;
しているだけです。
##②少し書き方をかえると?
function outer() {
let count = 1;
console.log(count);
return function inner() {
count++;
console.log(count);
}
}
let sum = outer(); // 1
sum(); // 2
sum(); // 3
1
2
3
何が変わったか?・・・return
を移動させて、function inner()
の前に付加しただけです。
勿論、変わらず動きます。
続いてこちら、
let outer = (function() {
let count = 1;
console.log(count);
return function inner() {
count++;
console.log(count);
}
})();
outer(); // 2
outer(); // 3
9行目の最後の()
でouter
を実行。
その後、outer()
でinner()
を実行しています。
こちらも、outer()
と```inner()``で切り離して実行できます。
#まとめ
クロージャーを使うことで、
・グローバル変数を減らすことで、他のプログラムからの干渉を受けずらく出来る
・処理回数が減らすことが出来る