Yanonoblog!

こつこつと

懐かしきJavaScriptのクロージャー

はじめに

JavaScriptクロージャーをかみくだいていきたいと思います。

JSPrimerを読んでいたら昔、勉強会で必死で理解しようとしていたのを思い出してもう一度振り返ってみようと思いました。w

朝会ではクロージャーに関する記事が3件残っていますね。

https://morning-6.hatenablog.com/entry/2021/12/23/100705

https://morning-6.hatenablog.com/entry/2021/12/21/105849

https://morning-6.hatenablog.com/entry/2021/12/18/091757

クロージャーとは

primer的説明

クロージャーとは「外側のスコープにある変数への参照を保持できる」という関数が持つ"性質"のことです。

朝会的説明

クロージャーとは静的スコープとメモリ管理の仕組みを利用して、関数内から特定の変数を参照し続けることで関数が状態を持てる仕組みのことを言います。

クロージャーが動く仕組み1:静的スコープ

JavaScriptでは静的スコープの仕組み上、識別子がどの変数を参照するかを静的に決めるという性質がある。 一方で関数の実行時に決まる仕組みを動的スコープという。

クロージャーが動く仕組み2:メモリ管理の仕組み

JavaScriptにはどこからも参照されなくなったデータを不要なデータと判断して自動的にメモリ上から解放するガベージコレクションという仕組みがある。

関数の中で作成したデータはその関数の実行が完了したらメモリが開放されるケースとされないケースがある。

以下の場合は変数tempArrayが参照している配列オブジェクトは、createArray関数の実行終了後も変数arrayから参照され続けています。 ひとつでも参照されているならば、そのデータが自動的に解放されることはありません。

function createArray() {
    const tempArray = [1, 2, 3];
    return tempArray;
}
const array = createArray();
console.log(array); // => [1, 2, 3]

クロージャ

function createCounter() {
    let count = 0;
    function increment() {
        count = count + 1;
        return count;
    }
    return increment;
}
const myCounter = createCounter();
myCounter(); // => 1
myCounter(); // => 2

const newCounter = createCounter();
newCounter(); // => 1
newCounter(); // => 2
  • myCounterに関数を代入しているが、このmyCounterがincrement()の外側のスコープの変数を参照するかはコードを実行する前(静的)に決まっている。
  • myCounterはincrementをreturnする。incrementはcountをreturnする。外側のスコープにあるcountは常にincrementから参照されている。直接returnしなくても外側の変数を参照していれば良い

JSPrimerの引用

このようにcount変数が自動解放されずに保持できているのは「increment関数内から外側のcreateCounter関数スコープにあるcount変数を参照している」ためです。 このような性質のことをクロージャー(関数閉包)と呼びます。クロージャーは「静的スコープ」と「参照され続けている変数のデータが保持される」という2つの性質によって成り立っています。

クロージャー自分の言葉で簡潔に

変数に代入して特定の変数を参照し続けている状態となっている一意な関数のことをクロージャーという。

クロージャーの役割とメリット

  1. 関数に状態を持たせる手段として
  2. 外から参照できない変数を定義する手段として
  3. グローバル変数を減らす手段として
  4. 高階関数の一部として

下記のコードは1~3のメリットがわかりやすい。

const createCounter = () => {
    let privateCount = 0;
    return () => {
        privateCount++;
        return `${privateCount}回目`;
    };
};
const counter = createCounter();
console.log(counter()); // => "1回目"
console.log(counter()); // => "2回目"
  • pricateCountはクロージャーによってグローバルスコープからは参照されない。(グローバルに余計な変数を定義せずに済む)

let privateCount = 0;の下にconsole.log(privateCount);とすれば分かるが counter()を何度実行しても変数の値は変わっていない。

これはあくまで内側の関数実行時に参照される変数は見えないメモリに記録されているからと仮説している。

クロージャーの役割とメリット - 高階関数の一部として

前提として"関数を返す関数"のことを高階関数と呼びます。

以下は変数に格納して2回目の実行で成り立つ関数です。

function greaterThan(n) {
    return function(m) {
        return m > n;
    };
}
// 5より大きな値かを判定する関数を作成する
const greaterThan5 = greaterThan(5);
console.log(greaterThan5(4)); // => false
console.log(greaterThan5(5)); // => false
console.log(greaterThan5(6)); // => true

変数に代入してクロージャーとなった関数にmとなる値を渡すことでnより大きいかを判定する高階関数を作れます。

greaterThan(5)を変数に格納した時点でnに5が入ります。

function(m)は値が入らないため、実行されずなにも出力されません。

グローバルに定義しても良いならば以下のようにクロージャーを表現することも出来ます。返り値は実行する度に1,2...となります。

let count = 0;

const yano = () => {
  return function increment () {
    count = count + 1;
    return count;
  };
};

const myCounter = yano();

おしまい

コメント

本記事の内容は以上になります!

このあたりの挙動は理解できていると意図しないバグなどの沼にハマりにくくなるかもしれません。


プログラミングスクールのご紹介 (卒業生より)

お世話になったプログラミングスクールであるRUNTEQです♪

https://runteq.jp/r/ohtFwbjW

こちらのリンクを経由すると1万円引きになります。

RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

https://twitter.com/outputky