JavaScriptのメカニズム（スコープ）

# 参考
[【JS】ガチで学びたい人のためのJavaScriptメカニズム](https://www.udemy.com/course/javascript-essence)

# あらすじ
現場でrailsを触ってlaravelを触って今はtypescriptとreact-routerで実装することが多くなってきたが、今までJavascriptをがっつり勉強してなかったので復習も兼ねて上記講座を受けてみようと思った。

とある先輩から「エンジニアじゃない人間が聞いてもわかる程度の説明にしろ」という言葉をいただいたので、できるだけ頑張る。

# スコープについて
実行中のコードから値と式が参照できる範囲
- グローバルスコープ
- スクリプトスコープ
- 関数スコープ
- ブロックスコープ
- モジュールスコープ

# グローバルスコープとスクリプトスコープについて
```javascript
let a = 0;
var b =0;
function c(){}
```
こうするとchrome開発者ツールのsourceのScope欄に
- Script a: 0
- Global: window (b: 0....c: f c())
のような形で入る。このことから
`console.log(b)`は`console.log(window.b)`と同義。
グローバルオブジェクトはwindowを省略できる。
**window自体がグローバルスコープ**
**一般的にはスクリプトスコープもグローバルスコープと呼ばれる**

# 関数スコープとブロックスコープ
関数スコープ
```javascript
function f(){
    //この部分が関数スコープ
}
```
```javascript
function f(){
    const b = 0;
}
console.log(b);

// Uncaught ReferenceError
```
これは関数スコープ内で`b`が宣言されているため、外では使えない。

# ブロックスコープとは
```javascript
{
//ここのこと
}
```
ルールとしては**let**または**const**を使うこと
`var`を使うとブロックスコープが適用されない。
~~関数もブロックスコープが適用されないので注意。~~
↑厳格モード、モジュールで使用可能
```javascript
{
    const b = 0;
    console.log(b);

    // 出力
    // 0

    const c = 0;
}
console.log(c);
// 出力
// ReferenceError
```
以下の場合はvarとfunctionはブロックスコープが適用されない。
```javascript
{
    var a = 'hoge';
    function b(){
    console.log('d is called');
    }
}
console.log(a);
b();

// 出力
// hoge
// d is called
```

ブロックスコープの用途としては`if`や`for`と一緒に使う

# スコープと実行コンテキストのまとめ
### スコープ
**実行中のコードから見える範囲**
### 実行コンテキスト
**コードが実行される状況**

# レキシカルスコープ
プログラムの文脈ではソースコードのどこに何を書いているかという意味。
コードを書く場所によって参照できる変数が変わるスコープのこと。
コードを記述した時点で決定するので**レキシカルスコープ**という。

```javascript
let a = 0;
function fn1(){
    let b = 1;
    function fn2(){
        let c = 3;
    }
    fn2
}
fn1
```
上記は全て出力できるa, fn1>b,fn2,>cのような関係。
```javascript
let a = 0;
function fn1(){
    let b = 1;
}
fn1

function fn2(){
    let c = 3;
}
fn2
```
上記のように外に出すとエラーになる。
これが**レキシカルスコープ**

```javascript
let a = 0;  // グローバルスコープ

function fn1() {
    let b = 1;  // fn1のスコープ
    
    function fn2() {
        let c = 2;  // fn2のスコープ
        console.log(a); // グローバルスコープの変数にアクセス可能
        console.log(b); // 外側のfn1スコープの変数にアクセス可能
        console.log(c); // 自身のスコープの変数にアクセス可能
    }
    
    fn2();
    // console.log(c); // エラー：cにはアクセスできない
}

fn1();
// console.log(b); // エラー：bにはアクセスできない
```

関数コンテキストの外部変数=レキシカルスコープである。
レキシカルスコープとは、実行中のコードからみた外部スコープのことである。
上記の例だとcのレキシカルスコープは
- 自身のスコープ(c)
- fn2の関数スコープ
- fn1の関数スコープ(b)
- グローバルスコープ(a)
と言える。

上記は**スコープチェーン**と言える。
※スコープチェーンは複数のスコープが連なっている状態

### おまけ
```javascript
let a = 2;
window.a = 4;
function fn1(){
    console.log(a);
}
```
この場合は2が出力される。スコープは内側から探すので、グローバルオブジェクトのwindowはletのスクリプトスコープより外側にあるのでletの方が入るという仕組み。

# クロージャーについて
クロージャーとは**レキシカルスコープの変数を関数が使用している状態**
```javascript
function fn1(){
    let b = 1;
    function fn2(){
        console.log(b);
    }
}
fn1
```
fn2のbの状態をクロージャーと呼ぶ。

# クロージャー（プライベート変数）について
```javascript
  let num = 0;
  function increment() {
    num += 1;
    console.log(num);
  }
```
上記の実装をするとnumを好きなタイミングですり替えられる
```javascript
  let num = 0;
  function increment() {
    num += 1;
    console.log(num);
  }
  
  increment();
  increment();

  num = 0;

  increment();

  // 出力例
  // 1
  // 2
  // 1
```
上記を避けるためにプライベート関数を使う
```javascript
function incrementFactory() {
  let num = 0;
  function increment() {
    num += 1;
    console.log(num);
  }
  return increment;
}

const increment = incrementFactory();
increment();
increment();
increment();
```
このようにすると、const宣言した際に`incrementFactory()`が実行され、
以下の`increment()`はconst宣言したincrementを実行する流れになる。
こうすることで初回に0がセットされ、以降のincrement()は戻り値のincrementが実行されるので1,2,3,4と処理が出力される。
`num = 0`はこの実装だとスコープ外なのでnumも代入できないので強い。

# クロージャー（動的な関数生成）について
```javascript
function addNumberFactory(num) {
  function addNumber(value) {
    return num + value;
  }
  return addNumber;
}

const add5 = addNumberFactory(5);
const result = add5(10);
console.log(result);
```
上記は`add5`宣言でaddNumberFactory(num)のnumに5が入り、
以降のadd5(10)ではaddNumberFactory(5)、addNumber(value)が返っている状態。
Factoryに渡す値によって生成される値が変わるので動的と呼ばれる。

# 即時関数
即時関数とは、関数定義と同時に一度だけ実行される関数
```javascript
let result =
    (function(仮引数){
        return 戻り値;
    })(実引数)
```
即時関数の特徴は**実行結果が呼び出し元に返却される**

~~()をつける理由は、`function hoge()`と基本は命名しないといけないが、~~
↑（関数式の場合は中で利用しない限りは名前をつけなくてよい)
ご指摘ありがとうございます！
補足：
- 関数宣言　例
```javascript
function hello(name) {  
    return `Hello, ${name}!`;
}

// 名前なしはエラー、
function(name) {  // SyntaxError
    return `Hello, ${name}!`;
}
```

- 関数式 例
```javascript
// 名前なしOK
const goodbye = function(name) {
    return `Goodbye, ${name}!`;
};
```

即時関数は一度だけ実行する際に名前をつけるのが面倒な場合、
名前を省略して()を繋げることができる。
```javascript
let c = (function() {

	let privateVal = 0;
	let publicVal = 10;

	function privateFn() {
		console.log('privateFn is called');
	}
	function publicFn() {
		console.log('publicFn is called: ' + privateVal++);
	}
	
	return {
		publicVal,
		publicFn
	};
})()
```
この場合、`privateVal`と`privateFn()`は直接外からアクセスできない。
また、`privateVal`は`publicFn()`とクロージャーの関係である。
呼び出された初回でprivateValが実行されるので、複数`publicFn()`を呼ぶと0,1,2,3と増えていく。