스코프 체인과 호이스팅

이를 이해하기 위해 자바스크립트 엔진에 대해 이해하면 좋은데, 이 글을 참고하자.

함수 레벨 스코프 : var

자바스크립트 실행은 함수에 따라 (A) 컴파일 단계, (B) 수행 단계가 재귀적으로 이뤄진다. 

처음 자바스크립트 실행 시 main() 함수에 대한 (A), (B) 처리를 시작으로 

내부에 새로운 함수 호출이 일어나면 새 함수에 대한 (A), (B) 처리, 그리고 또 내부 함수 호출이 있다면

그 함수에 대한 (A), (B) … 이런식으로 처리를 반복한다.


특정 함수 내 변수 var 의 선언은 본 함수 (A) 컴파일 단계에 정의되기 때문에 변수 var 의 스코프는 함수 레벨이 된다.
if, for 문과 같은 블록 레벨({}) 단위 변수를 위해 ES6 스펙에선 블록 레벨 스코프 const, let이 새로 소개되었다.

스코프 체인

자바스크립트 엔진 실행 과정에서 살펴보았듯 특정 함수에 대한 (B) 수행 단계에서 변수 할당 시,

본 함수의 Heap 영역에 변수 선언이 되어있는지 먼저 검사한다. 

만약 본 함수 내 변수가 선언되어있지 않았다면 해당 함수의 Heap 에서는 변수 선언을 찾을 수 없다. 

이때 해당 함수가 호출되기 이전의 함수로 (hidden) A pointer for previous scope 를 통해 올라가면서 

해당 함수 Heap Scope 에 변수가 선언되었는지 확인한다. 

어떠한 함수에서도 변수 선언이 되어있지 않다면 가장 처음에 호출된 main() 함수까지 올라가면서 검색한다. 

함수 호출 스택에 따라 가장 처음의 main() 함수까지 각 함수 Heap Scope 에 변수 선언 존재여부를 연쇄적으로 Chaining 하며 찾기때문에 이를 스코프 체인 이라고 부른다.

호이스팅

호이스팅은 변수나 함수의 선언이 끌어올려지는 것처럼 보이는 것을 말한다.

 

(A) 컴파일 단계에서 변수를 선언을 먼저하고, 다음 (B) 수행 단계에서 변수를 할당하기 때문에 같은 function-level 이라면 

아래와 같이 변수 선언과 할당을 나누어서 하더라도 자바스크립트 엔진에서는 변수 선언이 먼저 된 것으로 처리된다.

a = 10
var a;
--------------------------
# Global Scope (window)
- a = 10

위 예시처럼 var a 선언이 같은 함수 레벨 내에서 최상단에 ‘말려올라간것’처럼 수행되기도 하지만,

만약 함수 내 변수가 선언되어있지 않았다면 Scope Chain 을 통해 main() 함수까지 올라가면서 변수 선언을 찾는다. 

최종적으로 main() 함수 Heap Scope 에도 선언되어있지 않다면 main() 함수 영역에 변수를 선언해준다. 

main() 에서 호출한 어떤 함수이든 Scope Chain 을 통해 방금 선언해준 변수를 바라볼테니 이는 전역 변수인것이다.

(main() 의 Heap Scope 영역 명칭은 Global Scope (window)이기도 하다.)

특정 함수내에 변수를 할당하였지만 본 변수는 어느 함수에도 존재하지 않는 변수이기에 main() 함수까지 ‘말려올라가서’ 전역 변수를 선언한것이 된다.

변수 선언이 ‘말려올라갔다’는 의미에서 이 모든 경우를 호이스팅 이라고 표현한다.

 

 

 

출처)

 

Javascript 엔진 개요 및 실행 과정으로 살펴보는 Hoisting 과 Closure

자바스크립트자바스크립트는 웹 페이지의 세 요소중 하나입니다. HTML: 웹 페이지(문서) 포맷을 정의하는 마크업 언어 CSS: 웹 페이지(문서)의 디자인 요소에 대한 언어 Javascript: 웹 페이지(문서)와

aaronryu.github.io

 

자바스크립트 엔진

JavaScript 엔진은 Stack 메모리와 Heap 메모리를 사용하며 싱글 스레드로 모든 코드를 수행한다.

 

 

자바스크립트 엔진의 Stack 은 일반 프로그램 언어들의 Stack 과는 다르다.

타 프로그램 언어들은 함수 실행에 따라 Call Stack 에 각 로컬 함수들의 변수 등의 Context 정보들을 다 같이 쌓으며,

로컬 함수에만 국한된 정보들을 갖는다는 이유로 Context 를 Scope 라고도 부른다.

 

자바스크립트 엔진도 Call stack 에 함수 호출 순서를 적재하지만,

변수 및 함수 선언과 할당 정보는 Heap 에 따로 저장하여 Call Stack 에는 본 Heap 에 대한 포인터만 갖고 있다.

구체적으로 정리하면 아래와 같다.

 

  1. Heap: 각 함수 별 선언 및 할당되는 모든 변수 및 함수를 적재하는 메모리 영역
  2. Stack(Call Stack): 함수 실행 순서에 맞게 위 Heap 에 대한 포인터 적재 및 실행

자바스크립트 엔진 실행 과정

자바스크립트 엔진 실행 과정은 (A) JIT 컴파일 단계 (B) 수행 단계 이렇게 두 개로 나뉜다.

(A) 컴파일 과정

컴파일 과정에선 변수 및 함수의 '선언(Declaration)'만 추출하여 Heap 에 적재한다.
변수와 함수의 선언을 자바스크립트 실행 이전에 컴파일로 저장하여 실제 실행 시 변수와 함수 선언 여부를 검색한다.

 

예를 들어 아래 자바스크립트 파일을 처음 실행하게 되면 파일 전체에 컴파일 단계를 수행한다.

var a = 2;
b = 1;

function f(z) {
  b = 3;
  c = 4;
  var d = 6;
  e = 1;

  function g() {
    var e = 0;
    d = 3*d;
    return d;
  }

  return g();
  var e;
}

f(1);

 

1. 자바스크립트 첫 실행을 위한 main() 함수의 Global Scope(window) 영역을 Heap 에 생성한다.

# Global Scope (window)
- 
-

2. 변수 선언 var a을 찾아서 Global Scope(window) 영역에 a 를 적재한다.
3. 변수 할당 b = 1은 할당이므로 본 영역에 b 를 적재하지 않는다.

# Global Scope (window)
- a =
-

4. 함수 선언 function f(z)을 찾아서 Global Scope(window) 영역에 f 를 적재한다.
5. 함수 적재시엔 f 함수의 바이트코드(blob)에 대한 포인터값을 함께 적재한다.

 

자바스크립트 코드를 첫번째 라인에서 20번째 라인까지 컴파일 단계를 마치면 Heap 구성은 아래와 같다.

# Global Scope (window)
- a =
- f = a pointer for f functions bytecode

 

(B) 수행 과정

수행 과정에선 변수의 '할당(Assignment)'값들을 Heap 에 적재하고 함수는 호출 및 실행한다.

 

매 함수 호출때마다 스택에 함수 내 변수 및 함수를 같이 적재하는 스택 베이스 언어과 달리

자바스크립트는 스택에는 함수 호출 순서와 실제 변수 및 함수 정보들은 Heap 에 대한 포인터를 갖는다. 

Heap 에 함수 a() 를 위한 Local Execution Scope 는 a() 함수가 호출되기 이전에 Heap 에 존재했던

Global Scope(window)에 대한 포인터를 갖고있어서 엔진 내에서 아래와 같은 처리가 가능하다.

  • a() 함수 내에서 a = 1 변수 할당 시 먼저 Local Execution Scope 에 a 변수의 선언을 찾고,
    존재하지 않는다면 이전 Global Scope 로 돌아가 검색할 수 있다.
  • a() 함수 실행이 끝나게 되면 Call Stack 을 통해 현재 Heap 영역을 Global Scope 로 다시 되돌린다.


위에서 예시로 살펴본 자바스크립트 파일에 컴파일 단계를 마친 뒤 수행 단계는 아래와 같이 진행된다.

6. 컴파일 이후 아래의 Heap 을 갖고 다시 자바스크립트 파일 코드의 맨 첫번째 라인에서 실행이 시작된다.

# Global Scope (window)
- a =
- f = a pointer for f functions bytecode

7. 변수 할당 a = 2을 찾아서 Global Scope (window) 영역에 변수 a 존재 여부를 확인한다.
8. 변수 a 가 존재하므로 해당 a 에 2 를 할당한다.

# Global Scope (window)
- a = 2
- f = a pointer for f functions bytecode

9. 변수 할당 b = 1을 찾아서 Global Scope (window) 영역에 변수 b 존재 여부를 확인한다.
10. 변수 b 가 선언되어있지 않아 b 선언 및 1 을 할당한다.

# Global Scope (window)
- a = 2
- f = a pointer for f functions bytecode
- b = 1

11. 함수 호출 f(1)을 찾아서 Global Scope(window)영역에서 f() 선언 여부를 확인한다.
12. 함수 f() blob 컴파일 및 수행을 위해 Heap 에 새 Local Execution Scope 영역을 생성한다.

# Global Scope (window)
- a = 2
- f = a pointer for f functions bytecode
- b = 1

# Local Execution Scope for f()
- (hidden) A pointer for previous scope (= Global Scope (window))
- 
-

f(1) 함수 실행 시 새로이 생성된 Local Execution Scope에 다시 컴파일 단계를 통해  변수와 함수를 적재하고 수행 단계을 거친다.

또 f(1) 함수 내부에 또 다른 함수가 있다면 이 과정을 계속해서 재귀적으로 반복한다.

13. 함수 f() 의 컴파일 단계를 마치면 아래와 같다.

# Global Scope (window)
- a = 2
- f = a pointer for f functions bytecode
- b = 1

# Local Execution Scope for function f()
- (hidden) a pointer for previous scope (= Global Scope (window))
- z = 
- d = 
- e =

14. 함수 f() 의 수행 단계를 마치면 함수 f() 내 변수 할당 및 함수 g() 의 Scope 가 생성된다.

# Global Scope (window)
- a = 2
- f = a pointer for f functions bytecode
- b = 3

# Local Execution Scope for function f()
- (hidden) a pointer for previous scope (= Global Scope (window))
- z = 1
- d = 6
- e = 1
- c = 4

# Local Execution Scope for function g()
- (hidden) a pointer for previous scope (= Local Execution Scope for function f())
- e =

 

 

 

출처)

 

Javascript 엔진 개요 및 실행 과정으로 살펴보는 Hoisting 과 Closure

자바스크립트자바스크립트는 웹 페이지의 세 요소중 하나입니다. HTML: 웹 페이지(문서) 포맷을 정의하는 마크업 언어 CSS: 웹 페이지(문서)의 디자인 요소에 대한 언어 Javascript: 웹 페이지(문서)와

aaronryu.github.io

 

async 와 await

async/await 구문은 Promise 객체를 다루는 코드(Promise Chaining 코드 등)를 사람들이 좀더 익숙하게 느끼는

동기 실행 스타일의 코드로 작성할 수 있게 해주는 Syntactic sugar이다.

아래는 promise 를 다루는 코드와 async/await 문법을 비교한것이다. 두 결과는 동일하다.

 

Promise

fetch('https://www.google.com')
    .then((response) => response.text())
    .then((result) => {
        console.log('a');
    })
    .catch((error) => {
    	console.log(error);
    })
    .finally(() => {
    	console.log('exit')
    });

 

async 와 await

async function fetchAndPrint() {
  try {
    const response = await fetch('https://www.google.com');
    const result = await response.text();
    console.log('a');
  } catch(error) {
    console.log(error);
  } finally {
    console.log('exit');
  }
}

fetchAndPrint();

 

Promise 객체가 나온 이유

자바스크립트를 공부하던 중 promise 객체를 공부하면서 promise 객체 이전엔 어떤 식으로 비동기 처리를 했고

어떤 문제점 때문에 promise 객체가 나온 이유에 대해 알아보기로 했다.

 


Promise 객체 이전

promise 객체가 나오기 이전에도 아래 메서드들처럼 비동기 처리를 할 수 있었다.

setTimeout(callback, milliseconds);
addEventListener(eventname, callback);

fetch 함수도 마찬가지로 promise 객체를 반환하지 않았다면 아래처럼 사용했을것이다.

fetch('https://first.com', callback)

만약 promise.then 으로 promise chaining 을 하듯 여러 비동기작업을 순차적으로 처리를 하려면

아래처럼 콜백 헬(callback hell) 이라고 하는 지옥의 피라미드를 만들었을 것이다.

fetch('https://first.com', (response) => {
  // Do Something
  fetch('https://second.com', (response) => {
    // Do Something
    fetch('https;//third.com', (response) => {
      // Do Something
      fetch('https;//fourth.com', (response) => {
        // Do Something
      });
    });
  });
});

지금 봐도 지옥같은 가독성에 Do Something 주석 자리에 실제 코드가 들어있었다면 더 끔찍했을 것이라 예상할 수 있다.

 


Promise 객체 이후

promise 객체가 나온 이후 위와 같은 콜백 헬을 아래처럼 바꿔서 비교적 가독성 높고 깔끔하게 처리할 수 있다.

fetch('https://first.com')
  .then((response) => {
    // Do Something 
    return fetch('https://second.com');
  })
  .then((response) => {
    // Do Something 
    return fetch('https://third.com');
  })
  .then((response) => { 
    // Do Something 
    return fetch('https://third.com');
  });

이 뿐 아니라 promise 객체가 제공하는 문법들 덕분에 비동기 작업 처리 시 더 세밀한 처리를 할 수 있게 되었다.

const 는 과연 불변한가?

자바스크립트를 공부하다가 불변이라는 개념이 나와서 자바의 불변객체가 떠올라 친숙해서 정리해보기로 했다.

const 가 불변한지에 대해 알아보기 앞서 자바스크립트의 변수 선언 방식에 대해 가볍게 알아보자.

 

var, let, const

자바스크립트에서 변수 선언에는 var, let, const 가 있다.

특징은 간단하게 보자면 아래와 같다.

var 는 딱 봐도 변화무쌍해보이고 사용 시 유지보수의 험난함이 예상되는데 역시나 안티패턴이라고 하니 쓰지 않도록 하자.

1. var

  • 변수 재선언 가능
  • 값 재할당 가능
var a = 1;

var a = 2;  // 가능
a = 3;      // 가능

console.log(a);
-> 3 출력

2. let

  • 변수 재선언 불가능
  • 값 재할당 가능
let a = 1;

let a = 2;  // 불가능
a = 3;      // 가능

console.log(a);
-> 3 출력

3. const

  • 변수 재선언 불가능
  • 값 재할당 불가능
const a = 1;

const a = 2;  // 불가능
a = 3;        // 불가능

console.log(a);
-> 1 출력

 


그렇다면 const 는 불변(immutable) 한가?

답은 아니오다. 물론 문법 자체가 그렇듯 const 에 값 재할당은 불가능하다. 

하지만 주목할건 const 의 주소값(= 참조값) 재할당이 불가능한것이지 const 가 Object 라면 아래와 같이

Object 내의 값이나 상태는 얼마든지 변할 수 있다.

const obj = {};

obj['property'] = 1;

console.log(obj);
-> {property: 1} 출력

 

위에서 보았듯 obj 의 내부값이나 상태는 변경 가능하므로 불변하다고 할 수 없다.

+ Recent posts