방춘덕(고양이 키우면 지을 이름)의 개발 블로그입니다.
hoisting 본문
이 내용은 이전에 작성했던 execution context와 execution context stack과 execution context의 생성과정 글과 관련이 있습니다.
Hoisting is JavaScript's default behavior of moving declarations to the top.
(hoisting은 선언을 맨 위로 이동시키는 JS의 기본 동작이다.)
- https://www.w3schools.com/js/js_hoisting.asp
1). JS의 선언과 hoisted
먼저 예제를 보고 시작해보자.
console.log(hello) // undefined
var hello = 'HI!'
왜 다른 언어들과 같이 error를 출력하지 않고 undefined로 출력될까? execution context와 내용에서 다룬 것과 같이, JS에서는 코드를 직접적으로 실행하기 전에 코드를 읽으며 모든 선언들 (변수, 함수 등)을 메모리에 기록하여 값을 미리 undefined로 정의해놓기 때문이다.
var hello
console.log(hello)
hello = 'HI!'
위와 같이 변환된다고 개념적으로 이해하면 된다. 여기서 헷갈리지 않아야 할 점은 위와 같이 물리적으로 (즉, 코드에서) 최상단으로 이동하는 것이 아닌, 실제 코드에서 작성한 바로 그 위치에서 메모리에 올라간다.
그럼 hello 변수에 언제 "HI!"란 값을 저장할까? 바로 코드를 실행시키며 var hello = 'HI!'를 지나갈 때이다. JS에서는 코드를 실행시키며 값을 대입하므로 그 이전까지의 선언은 variable environment에 undefined로 저장된다.
2). JS의 초기화는 hoisting이 아니다.
JS는 선언만 hoisting 할 뿐, 초기화에서는 그렇지 않다. 아래의 예제를 보자
var a = 10
var b = 20
console.log('a:', a, 'b:', b) // a: 10 b: 20
var a = 10
console.log('a:', a, 'b:', b) // a: 10 b: undefined
var b = 20
왜 두 코드의 결과 값의 차이가 날까? 그 이유는 누누이 언급했듯 hoisting은 선언만 상단으로 올리고 초기화는 해당 라인을 실행할 때 이루어 지기 때문이다.
var a = 10
var b
console.log('a:', a, 'b:', b) // a: 10 b: undefined
b = 20
따라서 개념적으로는 위와 같이 이루어진다.
3). 함수에는 어떨까?
이제 변수에 관련해서 hoisting이 어떤 의미인지는 이해했으니, 함수 선언과 관련해서 알아보자. 사실 크게 다를 것도 없다.
futureCatName('왕춘식') // 내 미래 고양이 이름은 왕춘식이다.
function futureCatName(name) {
console.log(`내 미래 고양이 이름은 ${name}이다.`)
}
function futureCatName(name) {
console.log(`내 미래 고양이 이름은 ${name}이다.`)
}
futureCatName('왕춘식') // 내 미래 고양이 이름은 왕춘식이다.
위 코드들 또한 함수의 선언부(구현부 제외)가 hoisting에 의해 미리 상단에 선언되어 이미 상단에 선언되어 있고, 이후 execution phase에서 코드를 실행시키기 때문에 정상적으로 실행된다.
그렇다면 매개변수는 왜 잘 넘어갈까? 이 부분 또한 이전 글을 읽었다면 이해가 쉬웠을 수도 있다. JS에서 변수는 이전 execution context의 variable object를 참조하기 때문에 이미 futureCatName 함수의 execution context creation phase에서부터 name에 "왕춘식"이란 값이 대입돼 있기 때문이다.
4). 선언이 중복된다면 어떨까?
hoisiting은 선언을 상단으로 옮긴다. 하지만 선언들이 중복일 땐 어떻게 될까?
function A() {
console.log('A1')
}
A() // A2
function A() {
console.log('A2')
}
이 코드는 아래와 같이 개념적으로 표현된다. (다시 한번 강조하지만 절대 물리적으로 이렇지 않다. 이해하기 쉽게 표현하기 위한 것이다.)
var A
A = function () {
console.log('A1')
}
A = function () {
console.log('A2')
}
A() // A2
함수 식별자가 겹칠 경우, 가장 아래의 함수 선언부가 연결되므로 결국은 "B2"가 출력된다.
var A = function () {
console.log('A1')
}
A() // A1
var A = function () {
console.log('A2')
}
그렇다면 이 코드는 왜 "A1"이라는 결과를 출력할까? 다시 한번 개념적으로 표현한 코드를 봐보자.
var A
A = function () {
console.log('A1')
}
A()
A = function () {
console.log('A2')
}
변수를 함수로 초기화한다고 기존 변수일 때 hoisting 규칙이 사라지는 것은 아니다. 따라서 A1의 함수가 execution phase에서 더 먼저 초기화가 됐으므로 A1이 출력됨이 타당하다.
var A = function () {
console.log('A1')
}
A() // A1
var A = function () {
console.log('A2')
}
A() // A2
따라서 코드를 이렇게 변경하면 "A1", "A2" 모든 결과를 볼 수 있다.
이 글을 작성할 때 읽은 감사한 자료들.
http://chanlee.github.io/2013/12/10/javascript-variable-scope-and-hoisting/
https://www.w3schools.com/js/js_hoisting.asp
https://developer.mozilla.org/ko/docs/Glossary/Hoisting
http://insanehong.kr/post/javascript-function/
'JS' 카테고리의 다른 글
prototype과 prototype chain (0) | 2019.12.16 |
---|---|
scope (1) | 2019.12.13 |
event loop (0) | 2019.12.11 |
execution context의 생성과정 (0) | 2019.12.05 |
execution context 와 execution context stack (0) | 2019.12.02 |