> For the complete documentation index, see [llms.txt](https://sjsjsj1246.gitbook.io/javascript-deep-dive/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sjsjsj1246.gitbook.io/javascript-deep-dive/13.md).

# 13장 스코프

## **13.1 스코프 Scope (유효범위)**

* 자바스크립트의 스코프는 다른 언어의 스코프와 구별되는 특징이 있다.
* var 키워드로 선언한 변수와 let 또는 const 키워드로 선언한 변수의 스코프도 다르게 동작한다.

```jsx
function add(x, y) {
  //매개변수는 함수 몸체 내부에서만 참조할 수 있다.
  //즉, 매개변수의 스코프(유효범위)는 함수 몸체 내부이다.
  console.log(x + y); // 2 5
  return x + y;
}

add(2, 5);

//매개변수는 함수 몸체 내부에서만 참조할 수 있다.
console.log(x, y); // ReferenceError : x is not defined
```

```jsx
var var1 = 1; //코드 가장 바깥 영역에서 선언한 변수

if (true) {
  var var2 = 2;
  if (true) {
    var var3 = 3;
  }
}

function foo() {
  var var4 = 4; // 함수 내에서 선언한 변수
  function bar() {
    var var5 = 5; // 중첩된 함수 내에서 선언한 변수
  }
}

console.log(var1); // 1
console.log(var2); // 2
console.log(var3); // 3
console.log(var4); // ReferenceError : var4 is not defined
console.log(var5); // ReferenceError : var5 is not defined
```

모든 식별자 (변수이름, 함수이름, 클래스 이름 등)는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효범위, 즉 스코프가 결정된다.

```jsx
var x = "global"; // 전역 스코프

function foo() {
  var x = "local";
  console.log(x); // 1번 : foo 함수 스코프
}

foo();

console.log(x); // 2번
```

위 코드를 봤을 때, 1번과 2번 모두 x를 참조한다.

**식별자결정** : 자바스크립트 엔진은 이름이 같은 두 변수 중 어떤 변수를 참조해야 할 것인지를 결정

따라서 `스코프란 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙`이라고도할 수 있다.

💡 코드의 문맥은 렉시컬환경으로 이뤄진다. 이를 구현한 것이 실행컨텍스트이며, 모든 코드는 실행컨텍스트에서 평가되고 실행된다.

> `스코프는 네임스페이스다.`

스코프 내에서 식별자는 유일해야 하지만, 다른 스코프에는 같은 이름의 식별자를 사용할 수 있다.

* **var 키워드로 선언한 변수의 중복선언** var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언이 허용된다. 이는 의도치 않게 변수 값이 재할당되어 변경되는 부작용을 발생시킨다.

```jsx
function foo() {
  var x = 1;
  // var로 선언된 변수는 같은 스코프 내에서 중복선언이 허용된다.
  // 아래 변수 선언문은 자바스크립트 엔진에 의해서 var 키워드가 없는 것처럼 동작한다.
  var x = 2;

  console.log(x);
}

foo(); // 2
```

let이나 const 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용하지 않는다.

```jsx
function bar() {
  let x = 1;
  let x = 2;
}

bar(); //SyntaxError: Identifier 'x' has already been declared
```

## **13.2 스코프 종류**

코드는 전역과 지역으로 구분할 수 있다.

| 구분 | 설명           | 스코프    | 변수   |
| -- | ------------ | ------ | ---- |
| 전역 | 코드의 가장 바깥 영역 | 전역 스코프 | 전역변수 |
| 지역 | 함수 몸체 내부     | 지역 스코프 | 지역변수 |

변수는 자신이 전언된 위치(전역/지역)에 의해 자신이 유효한 범위인 스코프(전역 스코프/지역 스코프)가 결정된다.

### 13.2.1 전역과 전역 스코프

**전역** : 코드의 가장 바깥 영역, 전역에 변수를 선언하면 전역 스코프를 갖는 **전역 변수**가 된다.

💡 전역변수는 어디서든 참조 가능하다.

### 13.2.2 지역과 지역 스코프

* **지역** : 함수 몸체 내부를 말한다. 지역에 변수를 선언하면 지역 스코프를 갖는 **지역변수**가 된다.

💡 지역 변수는 자신의 지역 스코프와, 하위 지역스코프에서 유효하다.

```jsx
var x = "global x";

function foo() {
  var x = "local";
  console.log(x); // local
}
foo();
console.log(x); //global
```

foo 함수 내부에서 선언된 x는 지역변수다. 지역변수 x는 자신이 선언된 foo 내부에서만 참조 가능하다. 하지만 이 지역변수 x를 전역 또는 foo 함수 내부 이외의 지역에서 참조하면 참조 에러가 발생한다.

그런데 foo 함수 외부에도 전역변수 x가 선언되어있다. 이 경우에 foo함수 내에서 x 변수를 참조하면 전역변수 x를 참조하는 것이 아니라 foo 함수 내부에 있는 x를 참조한다. 이는 자바스크립트 엔진이 **스코프 체인**을 통해 참조할 변수를 검색(identifier resolution)했기 때문이다.

## 13.3 스코프 체인

* 함수의 중첩 : 함수 몸체 내부에서 함수가 정의된 것
* 중첩 함수 : 함수 몸체 내부에서 정의한 함수
* 외부함수 : 중첩 함수를 포함하는 함수

함수가 중첩될 수 있기 때문에 지역 스코프도 중첩될 수 있다. 이는 **스코프가** 함수의 중첩에 의해 **계층적 구조**를 갖는다는 것을 의미한다.

중첩함수의 지역 스코프는 중첩 함수를 포함하는 외부 함수의 지역 스코프와 `계층적 구조`를 갖는다. 이때 **외부 함수의 지역 스코프**를 **중첩 함수의 상위 스코프**라고 한다.

💡 모든 스코프가 계층적으로 연결된 것을 스코프 체인이라고 한다. 모든 지역 스코프의 최상위 스코프는 전역 스코프이다.

```jsx
var x = "global x";

function outer() {
  var z = "outer's local z";
  console.log(x);

  function inner() {
    var x = "inner's local x";
    console.log(x); // "inner's local x"
    console.log(z); // "outer's local z"
  }
  inner();

  console.log(x);
}
outer();
```

* **스코프 체인은 물리적인 실체로 존재한다.**
  * 자바스크립트 엔진은 코드실행에 앞서 **렉시컬 환경**을 실제로 **생성**한다.
  * **변수 선언**이 되면 변수 식별자가 이 렉시컬 환경에 키로 등록된다.
  * **변수할당**이 일어나면 렉시컬 환경의 변수 식별자에 해당하는 값을 변경한다.
  * **변수 검색**도 이 렉시컬 환경 상에서 일어난다.
  * **변수를 참조할 때** 자바스크립트 엔진은 **스코프 체인**을 통해 변수를 참조하는 코드의 스코프에서 시작하여 **상위 스코프 방향으로 이동하며** 선언된 변수를 **검색**한다. 이를 통해 `상위 스코프에서 선언한 변수를 하위 스코프에서도 참조 가능하다.`
* **스코프 체인은 렉시컬 환경을 단방향으로 연결한 것이다.**
  * 전역 렉시컬 환경은 코드가 로드되면 곧바로 생성된다.
  * 함수의 렉시컬 환경은 함수가 호출되면 곧바로 생성된다.

### 13.3.1 스코프 체인에 의한 변수 검색

* 상위 스코프에서 유효한 변수는 하위 스코프에서 자유롭게 참조할 수 있다.
* 하위 스코프에서 유효한 변수를 상위 스코프에서 참조할 수 없다.
* 스코프의 계층적 구조는 부자 관계로 이뤄진 상속과 유사하다. ( 상속 : 상속을 통해 부모의 자산을 자식이 자유롭게 사용할 수 있지만 자식의 자산을 부모가 사용할 순 없다.)

### 13.3.2 스코프 체인에 의한 함수 검색

* 함수도 식별자에 할당되기 때문에 스코프를 갖는다. 사실 함수는 식별자에 함수 객체가 할당된 것 외에는 일반 변수와 다를 바가 없다.
* 따라서 스코프를 **식별자를 검색하는 규칙**이라고 표현하는 편이 좀 더 적합하다.

## 13.4 함수 레벨 스코프

지역은 함수 몸체 내부를 말하고 지역은 지역 스코프를 만든다. 이는 코드 블록이 아닌 \*\*`함수에 의해서만 지역 스코프가 생성된다`\*\*는 의미다.

* **블록 레벨 스코프(block level scope)** 모든 코드 블록(if, for, while, try/catch 등)은 지역 스코프를 만든다.
* **함수 레벨 스코프(function level scope)** **var 키워드**로 선언된 변수는 오로지 함수의 **코드 블록(함수 몸체)** 만을 **지역 스코프**로 인정한다.

```jsx
var x = 1;

if (true) {
  var x = 10; // x는 전역 변수.
}

console.log(x); // 10, 값이 재할당됨
```

var 키워드로 선언된 변수는 함수 레벨 스코프만 인정하기 때문에 `함수 밖에서 var 키워드로 선언된 변수는 코드 블록 내에서 선언`되었다고 하더라도 **전역변수**이다.

> 따라서 전역변수 x는 중복 선언되고, 그 결과 전역변수의 재할당이 일어난다.

## 13.5 렉시컬 스코프

```jsx
var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // 1
bar(); // 1
```

* **상위 스코프 결정 방법**
  * **동적 스코프** **함수를 어디서 호출했는지에 따라** 함수의 상위 스코프를 결정한다. `함수를 정의하는 시점에서는 함수가 어디서 호출될 지 알 수 없다.` 따라서 **함수가 호출되는 시점에 동적으로 상위스코프를 결정**해야 하기 때문에 동적스코프라 한다. 이 방식으로 위 코드의 상위스코프를 결정한다면 bar 함수의 상위 스코프는 foo함수의 지역 스코프, 그리고 전역 스코프일 것이다.
  * **렉시컬 스코프 / 정적 스코프 (자바스크립트 방식)** **함수를 어디서 정의했는지에 따라** 함수의 상위 스코프를 결정한다. 함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않는다. 즉, **함수의 상위 스코프는 언제나 자신이 정의된 스코프이다**. 함수의 상위스코프는 함수 정의가 실행될 때 정적으로 결정된다. 함수 정의가 실행되어 생성된 `함수 객체는 이렇게 결정된 상위 스코프를 기억한다.` 함수가 호출될 때마다 함수의 상위 스코프를 참조할 필요가 있기 때문이다. 이 방식으로 위 코드의 상위 스코프를 결정한다면 bar 함수의 상위 스코프는 전역스코프일 것이다
