0
0

More than 1 year has passed since last update.

【JavaScript】関数とスコープ② 〜巻き上げ・クロージャ〜

Posted at

概要

JavaScriptを学習、理解を深めるため「JavaScript Primer 迷わないための入門書」を読み、
理解した内容等を記載していく。

【JavaScript】JavaScript入門一覧」に他の記事をまとめています。

この記事で理解できること

  • 巻き上げとは
  • 関数における巻き上げの挙動
  • クロージャとクロージャ内部の仕組み

関数宣言と巻き上げ

  • 巻き上げとは、宣言部分がもっとも近い関数またはグローバルスコープの先頭に移動しているように見える動作のこと。
  • 代表的な例として、functionキーワードを使用した関数宣言より前に関数を呼び出しをしてもエラーにならない挙動がある。
// この時点では関数は定義されていないのに関数を呼び出せる
hello(); // => hello

// この位置で関数を定義している
function hello() {
  console.log("hello");
}

巻き上げによって以下のように定義されているのと同じ

// グローバルスコープに移動しているかのような挙動となる
function hello() {
  console.log("hello");
}

hello(); // => hello

クロージャ

  • 静的スコープ」「メモリ管理の仕組み」を利用し、関数内で外側のスコープにある変数への参照を保持できるという仕組みのこと。

静的スコープ

  • JavaScriptのスコープには、どの識別子がどの変数を参照するかが静的に決定されるという性質がある。
  • 以下の例では、下記の流れで静的に参照先が決定される。
    • 関数printStr()の関数スコープには変数strが定義されていない
    • 外側のスコープ(グローバルスコープ)を探す
    • グローバルスコープのstrを参照する(静的に参照が決定する)
const str = "グローバルスコープのstr";

// printStr関数内で定義されたstrは、スコープチェーンの仕組みによりグローバルスコープの変数strを参照する
function printStr() {
  console.log(str);
}

function excePrintStrMethod() {
  // この変数strはprintStr関数から参照されない
  const str = "関数スコープのstr";
  // printStr関数はグローバルスコープの変数strを参照することが決定している(静的スコープ)
  printStr();
}

excePrintStrMethod(); // => グローバルスコープのstr
excePrintStrMethod(); // => グローバルスコープのstr

メモリ管理の仕組み

  • プログラミング言語には、使わなくなった変数やデータをメモリから解放する仕組みを持っている。
  • 言語によっては、手動でメモリを解放するコードを書く必要がある。
  • JavaScriptでは、自動でメモリを解放してくれるガベージコレクションという仕組みを採用している。

メモリから不要なデータが解放されるタイミング例

// ①"first comment"という文字列データがメモリ上に確保され、変数strがメモリ上のデータを参照する
let str = "first comment";

// ②再代入により変数strは、"second comment"という文字列データを参照するようになる
str = "second comment";

// ③"first comment"は、どこからも参照されなくなりガベレージコレクションの回収対象になる
// ④任意のタイミングでガベレージコレクションが回収し、メモリから解放される

関数の実行が終了した際に解放される場合

// 関数内に定義した変数は関数呼び出しのたびに定義され、呼び出し完了時に不要となり解放される
function func() {
  const str = "関数内で定義";
  console.log(str);
}

func(); // => 関数内で定義

関数の実行が終了しても解放されない場合

// 関数内で定義した変数を返り値としている
function func() {
  const str = "関数内で定義";
  return str;
}

// 関数funcの返り値を変数resultに代入
const result = func();
console.log(result); // => 関数内で定義(変数resultはデータを参照し続けている)

静的スコープとメモリ管理の仕組みを使用したクロージャの例

const baseCounter = () => {
  let count = 0;
  return function increment() { // *1
    return count = count + 1;
  }
}

// counterAに代入したタイミングでbaseCounter()内の変数や関数が定義される
// counterAは"*1"のincrement()を参照している
// "*1"のincrement()はcountを参照しているため、結果的にcounterAも参照し続けている事になる
const counterA = baseCounter();
console.log(counterA()); // => 1
console.log(counterA()); // => 2
console.log(counterA()); // => 3
console.log(counterA()); // => 4

// counterBに代入したタイミングでbaseCounter()内の変数や関数が新たに定義される
// counterBを呼び出すと、結果は1からとなっており、新たな参照を持っていることが分かる
const counterB = baseCounter();
console.log(counterB()); // => 1
console.log(counterB()); // => 2
console.log(counterB()); // => 3
console.log(counterB()); // => 4

console.log(counterA()); // => 5
console.log(counterA()); // => 6

console.log(counterB()); // => 5
0
0
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
0
0