LoginSignup
3
1

More than 3 years have passed since last update.

【JS】やっと分かった、クロージャー。

Last updated at Posted at 2020-10-26

はじめに

【対象】
・クロージャーについて何度も調べて、実践しているが、なかなか理解できない人

・グローバル変数とローカル変数の違いはわかる人

・グローバル変数を出来るだけ使わないようにするのは分かるけど、やり方が分からない人

そもそも、クロージャーとは?

クロージャは、関数と、その関数が宣言されたレキシカル環境の組み合わせです。
MDNより

要は、

「関数を囲むスコープにある変数を参照できる関数」の事。

クロージャーの特徴

①関数が呼ばれるたびに結果が変わる事
②グローバル変数を減らすことで、他のプログラムからの干渉を受けずらく出来る
③処理回数が減らすことが出来る(※重要)
④レキシカルスコープの言語で使う

クロージャーの特徴について説明する前に。

まずは、レキシカルスコープとダイナミックスコープの違いからの説明です。
(分かる人は飛ばしてください。)

・レキシカルスコープとダイナミックスコープ

①レキシカルスコープ

→関数を定義した時点でスコープが決まる。

↓コードで説明

sample.js
var x = 10; 
function A(){
  console.log(x);
}
function B(){
  var x = 1000;
  A();
}
A();  // 10
B(); // 10

function A(){}として関数定義した時点で参照していたxが代入されている。

・メジャーな言語(JSなど)で採用される。

②ダイナミックスコープ(Perl等の言語)

実行時>定義時

→関数定義時にもスコープは決まるが、実行時のスコープ優先。

sample.js
var x = 10; 
function A(){
  console.log(x);
}

function B(){
  var x = 1000;
  A();
}
A();  // 10
B(); // 1000

クロージャーについて1からの説明

先程、記載した通り、

クロージャーとは、
関数を囲むスコープにある変数を参照できる関数の事です。

コードで説明すると、

sample.js
function outer() {
  let place = '大阪';
  console.log(place);

  function inner() {
    alert(place);
  }
  return inner;
}

let Func = outer();

Func();

上から順に説明していきます。
(基本的な所まですべて記述します)

let placeouter(){}のローカル変数。(※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()の中身を実行

【特徴①】関数が呼ばれるたびに結果が変わる事について

~【今回行う事】~
関数を呼び出すたびに、数が増えていく処理を行います。

通常の関数では?

【通常】

sample.js
function outer() {
    let count = 1;

    count++;
    console.log(count);
}

outer(); // 2
outer(); // 2
outer(); // 2

上記のコードでは、outer();を何度実行しても呼び出されるたびにcountには1が代入されるため同じ結果しか得れません。

【クロージャー使用】

sample.js
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

実現できました。

【特徴②】グローバル変数を減らすことで、他のプログラムからの干渉を受けずらく出来るについて

【特徴①】のコードと数字の増えるプログラムで説明すると、

下のようなコードでも動くのでは?

sample.js
let count = 1;
console.log(count);

function outer() {
    count++;
    console.log(count); // 1
}

outer(); // 2
outer(); // 3
outer(); // 4

let count = 1;を関数の外で定義しています。

グローバル変数として定義した場合も、思い通りの処理が完成しました!

しかし、

これでは、クロージャーを勉強している意味はありません。

そもそも、クロージャーを使う目的の一つとして

グローバル変数を使わないようにすることで、他のプログラムからの干渉を受けずらくという事があげられます。

グローバル変数を使うことで、変数名が被った際に参照先の違いで思わぬエラーが発生する可能性もあります。

上記のコードでは、グローバル変数countにアクセスしています

それをしないために、クロージャーを使ってカプセル化を行います。

【クロージャー使用】

sample.js
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からの説明」にあるコードを少し変えたのコードで説明します。

【クロージャーなし】

sample.js
function outer() {
  let place = '大阪';
  let word;
  // console.log(place);

  if(place === '大阪') {
    word = 'せやな!';
  } else {
    word = 'ちゃうやん';
  }

  window.alert(word)
}

outer();

上記のコードだと、outer();が呼び出されるたびに毎度ifで処理しないといけません。

しかし、、、

【クロージャーを使うと?】

sample.js
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ではなく、関数定義内で関数実行

sample.js
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 = 1count++;しているだけです。

②少し書き方をかえると?

sample.js
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()の前に付加しただけです。

勿論、変わらず動きます。

続いてこちら、

sample.js
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()で切り離して実行できます。

まとめ

クロージャーを使うことで、

グローバル変数を減らすことで、他のプログラムからの干渉を受けずらく出来る

・処理回数が減らすことが出来る

参考

【JavaScriptの基礎】レキシカルスコープとクロージャを理解する

私が今までクロージャを理解できなかった理由

【JavaScript入門】クロージャって一体何?使い方まで徹底解説

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1