【JS】スコープチェーンとクロージャー

目次

スコープチェーンとは

スコープチェーンの定義

スコープが複数階層で、連なっている状態。下記画像の状態。

スコープが複数階層になっている場合は、一番内側から変数を探しに行き、見つかった段階でその変数を取得する。

// 下記の場合、3が表示される
let a = 1;
function fn1() {
  let a = 2;
  function fn2() {
    let a = 3;
    console.log(a);
  }
  fn2();
}
fn1();

// 下記の場合、1が表示される
let a = 1;
function fn1() {
  // let a = 2;
  function fn2() {
    // let a = 3;
    console.log(a);
  }
  fn2();
}
fn1();

グローバルスコープとスクリプトスコープの場合

グローバルスコープは、スクリプトスコープより外側のスコープ。

// a = 2が出力される
let a = 2; // スクリプトスコープ
window.a = 4; // グローバルスコープ
function fn1() {
  function fn2() {
    console.log(a);
  }
  fn2();
}
fn1();

スクリプトスコープで起きやすいミス

// 下記の場合、letを使った変数より前に値を取得しようとしているので、エラーが発生する
let a = 2;
window.a = 4;
function fn1() {
  function fn2() {
    console.log(a);
    let a = 3;
  }
  fn2();
}
fn1();

letの場合はエラーが出るのでまだわかりやすい。
varの場合で、if文などブロックで囲まれているときに、varのホイスティングが働いてしまうため、aはundefinedとなる。

let a = 2;
window.a = 4;
function fn1() {
  function fn2() {
    console.log(a);
    if (true) {
      var a = 3;
    }
  }
  fn2();
}
fn1();

クロージャーとは

クロージャーの定義

レキシカルスコープの変数を関数が使用している状態。

内側に定義されている関数から、レキシカルスコープの変数への参照を保持している状態をクロージャーという。

// 関数f2から、変数bに対してレキシカルスコープの値を参照しに行っている。
function fn1() {
  let b = 3;
  function fn2() {
    console.log(b);
  }
  fn2();
}
fn1();

クロージャーを使用した関数の記述

  1. プライベート変数の定義
  2. 動的な関数の生成

プライベート変数の定義

【実装】 値がカウントアップされる仕組みを作る

increment();
increment();
increment();

function increment() {
  let num = 0;
  num = num + 1;
  console.log(num);
}
// 関数の中でnumを宣言しているため、常にnumが0となる。結果、1が3回表示される

関数が呼ばれるたびではなく、グローバルコンテキストが実行されるタイミングで初期化を行う。

let num = 0;

increment();
increment();
increment();

function increment() {
  num = num + 1;
  console.log(num);
}
// 1 2 3と表示される。が、この状態ではnumの変更が可能となっている。

関数の内部に変数を持ちながらも、外部からはアクセスできないようにしたい。

function incrementFactory() {
  let num = 0;

  function increment() {
    num = num + 1;
    console.log(num);
  }

  return increment; // incrementをincrementFactoryへ返却
}

const increment = incrementFactory(); // incrementFactoryの実行

increment();
increment();
increment();

関数の外側からはnumにアクセスすることができないため、書き換えられる心配がない。
numの初期化はincrementFactoryが実行されたタイミングでのみ行われる。

動的な関数の生成

異なる結果を返す関数を動的に作成することができる。

function addNumberFactory(num) {
  function addNumber(value) {
    return num + value;
  }
  return addNumber;
}

const add5 = addNumberFactory(5);
const result = add5(10);
console.log(result);

解説

const add5 = addNumberFactory(5);
// add5にはnumに5が設定されている状態の関数が格納される
function addNumberFactory(5) {
  function addNumber(value) {
    return 5 + value;
  }
  return addNumber;
}

const result = add5(10);
// add5(addNumberFactory(5))にはvalueの引数が取れるので、10を渡す。

const result = add5(10);
// return 5 + 10; で15となる。
const add10 = addNumberFactory(10);
const result = add10(10);
// 結果は20となる。

関数を作成する関数に渡す値によって、生成される関数が変わってくるので、動的な関数と呼ぶ。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

目次