TIL 10. JS environmentRecord와 호이스팅

rahula·2021년 5월 11일
0

javascript

목록 보기
1/15
post-thumbnail

javascript엔진이 식별자를 수집하는 과정에 대해서 알아보겠습니다. 이 글은 코어자바스크립트 책을 토대로 쓰여졌습니다.

엔진은 식별자를 어떻게 수집하는가?

어떤 실행 컨텍스트가 활성화될 때 JS엔진은 해당 execution context에 관련된 코드들을
실행하는 데 필요한 환경 정보들을 수집해서 execution context 객체에 저장한다.

lexicalEnvironment의 첫번째 수집 정보.

변수의 식별자 정보는 모두 environmentRecord에 수집된다.

environmentRecord

식별자 정보들이 저장됨. 일단 컨텍스트를 구성하는 것들의 식별자들이 대상임.

어떤 식별자가 담길까?

변수
매개 변수(parameter)와 인자(arguments)
선언된 함수 (함수 표현식은 제외.)
해당 컨텍스트 상에서 선언된 변수의 식별자, 선언된 함수 자체, 컨텍스트를 구성하는 함수에 지정된 매개변수(인자값) 식별자 등.

컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 변수 정보를 순서대로 수집함.

참고 : 전역 객체
전역 실행 컨텍스트는 변수 객체를 생성하는 대신 JS 구동 환경이 별도로제공하는 객체, 즉 global object를 활용한다. 브라우저의 window, NodeJS의 global 등이 있음. 이들은 JS 내장 객체가 아닌 host 객체로 분류된다.

예시 : 평소에 물처럼 쓰는 전역 객체들

console.log("hello world!")
console.log(window)
console.log(document)

호이스팅

자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다 라고 생각하더라도 코드를 해석하는 데는 문제될 것이 없다. 변수 정보를 수집하는 과정을 더욱 이해하기 쉬운 방법으로 대체한 가상의 개념이다.

예시 : var을 통한 변수 선언

const func =(x)=>{ // 수집 대상 1 
  console.log(x) 
  var x // 수집 대상 2
  console.log(x)
  var x = 2 // 수집 대상 3
  console.log(x)
}
func(1)

그런데 인자는 코드 내부에서 변수를 선언한 것과 다른 점이 없다. 그러므로 인자를 함수 내부의 다른 코드보다 먼저 선언 및 할당이 이뤄진 걸로 간주할 수 있다.

그리고 environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이

있는지에만 관심이 있지, 각 식별자에 어떤 값이 할당될것인지는 1도 관심 없다.

따라서 변수를 인식할 때 변수명(식별자)만 끌어올리고, 할당 과정은 원래 자리에 그대로 남겨둔다.

예시 : 매개변수가 변수 선언&할당과 같다고 간주하고, 호이스팅 과정을 볼 수 있다고 가정한 위의 코드

const func = ()=>{
    var x // (1) 수집 대상 1의 변수 선언 부분
    var x // (2) 수집 대상 2의 변수 선언 부분
    var x // (3) 수집 대상 3의 변수 선언 부분

x = 1 // (4) 수집 대상 1의 할당 부분
console.log(x)
console.log(x)
x = 2 // (5) 수집 대상 3의 할당 부분
console.log(x)
}
func()

(1) : 변수 x를 선언한다. 이때 메모리에서는 저장할 공간을 미리 확보하고, 확보한 공간의 주솟값을 변수 x에 연결해둔다.

(2)와 (3) : 다시 변수 x를 선언한다. 그러나 이미 선언된 변수 x가 있으므로 무시한다.

(4) : x에 1을 할당한다. 숫자 1을 별도의 메모리에 담고, x와 연결된 메모리 공간에 숫자 1을 가리키는 주솟값을 입력한다. 밑의 두 log함수 실행의 결과로 콘솔창에는 1이 두번 찍힌다.

(5) : x에 2를 할당한다. 밑의 log함수 실행의 결과로 콘솔창에는 2가 찍힌다.

var은 안 쓰는게 맞다.
let 혹은 const를 쓰면 하나의 변수에 대해서 중복되게 선언하는 실수가 일어나지 않도록 할 수 있다. 호이스팅이 정말 필요한 게 아닌 이상, 이 원칙을 지킨다면 이런 실수는 일어나지 않을거.

const func =(x)=>{ // 수집 대상 1
  console.log(x) 
  let x // 수집 대상 2
  console.log(x)
  let x = 2 // 수집 대상 3
  console.log(x)
}
func(1)
// Uncaught SyntaxError: Identifier 'x' has already been declared

함수 선언
선언부와 할당부를 나누어 선언부만 끌어올리는 반면, 함수 선언은 함수 전체를 끌어올린다.

sum(1,2) // (1)
multiply(1,2) // (2)
divide(1,2) // (3)

function sum (a,b) {
	return a + b
}

var multiply = function (a,b) {
	return a * b
}

const divide =  function (a,b){
	return a / b
}

(1) : 3

(2) : Uncaught TypeError: multiply is not a function

(3) : Uncaught ReferenceError: Cannot access 'divide' before initialization

왜 그럴까?

var sum = function sum (a,b) { // 함수 선언문은 전체를 호이스팅
	return a + b
}

var multiply // 함수 표현식은 선언문만 호이스팅

sum(1,2)
multiply(1,2)
divide(1,2)

var multiply = function (a,b) {
	return a * b
}

const divide =  function (a,b){ // var과 달리 const 혹은 let으로 선언된 변수는 호이스팅되지 않음.
	return a / b
}

함수도 하나의 값으로 취급할 수 있다. 함수 표현식은 함수를 다른 변수에 값으로써 할당한 것이다.

함수 선언문은 위험하다.

선언한 후에야 호출할 수 있는 것이 훨씬 자연스럽다. 그리고 함수 표현식은 위험하다. 실수로 값을 덮어쓸 수 있기 때문이다. 정말 호이스팅이 필요한 경우에만 써야 하고, 보는 사람도 함수 선언문을 볼 때 호이스팅을 하려는 의도를 알 수 있을거라고 생각한다.

추가 질문

  1. 호스트 객체란?

  2. 호이스팅이 필요한 상황은?

profile
백엔드 지망 대학생

0개의 댓글