javaScript/jsDeepDive

함수 호이스팅

부엉이사장 2023. 5. 23. 10:11

# 호이스팅이란?

https://jacobowl.tistory.com/136

 

변수 호이스팅, 그리고 초기화 initialize, Temporal Dead Zone

초기화는 변수선언시에 최초로 undefined가 할당이 되는것을 말한다. 먼저 그림으로 살펴보면, let value = 'hello world'; 라는 코드가 실행되면 먼저 식별자 value가 가르키는 메모리주소1이 생성되고 이

jacobowl.tistory.com

 

이전에 변수의 호이스팅에 대해서 포스팅을 했었다.

코드는 위에서 아래로 순차적으로 실행되지만 변수를 선언하기전에 참조가 가능한걸 변수호이스팅이라고 한다.

함수도 이렇게 정의(함수는 선언이 아니라 정의라고함)하기 이전에 호출이 가능한걸 함수 호이스팅이라고 한다.

 

 

# 함수 선언문에서의 호이스팅

function fuc() {
  console.log("hello fuc");
}

이렇게 함수선언문으로 함수fuc을 정의했다.

계속봐도 함수선언문이라는 이름은 참 ㅈ같이 만든것같다.. 그냥 함수정의문이라고 하지 좀 ㅡㅡ

암튼 함수선언문으로 함수를 정의하고 이전에 함수를 호출, 참조하면 어떻게 될까?

 

- 함수 호출

fuc();// hello fuc이래~

function fuc() {
  console.log("hello fuc");
}

이렇게 함수몸체내의 코드가 잘 실행되는것을 볼 수 있다.

이전 변수 호이스팅에서 살펴봤듯이 변수선언을 var로 했을때 undefined가 뜨고, let 으로 선언할시 초기화 에러가 뜨는게 아니라, 그냥 함수자체가 호출이 잘된다. 이게 변수호이스팅과 다른점이다. 단순해서 좋다.

함수객체가 런타임이전에 메모리에 값으로 생성되기때문에 그렇단다.

 

 

- 함수 참조

console.log(fuc); //function fuc이 나온다~

function fuc() {
  console.log("hello fuc");
}

뭐 얘도 함수객체 잘 가져온다.

 

 

 

 

 

# 함수 표현식에서의 호이스팅

const fuc = function () {
  console.log("hello fuc");
};

요렇게 함수를 함수표현식으로 정의해봤다~

그럼 호이스팅이 될까?

 

- 함수 참조

console.log(fuc); //초기화 참조에러가 뜹니다~

const fuc = function () {
  console.log("hello fuc");
};

초기화 참조 에러가 뜹니다. 

var로 함수를 정의했다면?

console.log(fuc); //undefined가 뜹니다.

var fuc = function () {
  console.log("hello fuc");
};

음 변수 호이스팅과 마찬가지로 undefined가 뜬다.

 

 

- 함수호출

fuc(); // 에러가 뜨네요!

const fuc = function () {
  console.log("hello fuc");
};

 

변수 호이스팅처럼 초기화문제로 참조에러가 뜬다.

그럼 var로 해본다면?

fuc(); // 타입에러 fuc은 함수가 아니래~

var fuc = function () {
  console.log("hello fuc");
};

위에 const로 함수를 정의한것과는 다르게 

타입에러가 뜹니다. fuc이 함수가 아니래요.

 

 

 

# 그럼 왜 이런차이가 발생하는것일까?

함수선언문으로 함수를 정의할때는 함수호이스팅이 발생한다. 사실 단순해서 더 설명할게 없다.

하지만 함수표현식으로 함수를 정의한다면 정확히 변수호이스팅이 발생을 한다.

https://jacobowl.tistory.com/171

 

함수 리터럴에 대해서

# 먼저 리터럴에 대해서 다시 살펴보자. https://jacobowl.tistory.com/137 값(value), 리터럴(literal), 표현식(expression), 문(statement) # 리터럴(literal) 리터럴은 사람이 알아보는 기호로 적은 표기법을 말한다.

jacobowl.tistory.com

위 포스팅에서 함수리터럴에 대해서 정확히 이해했다면

위 그림처럼 fuc이라는 식별자에 함수객체가 할당이 된거라는걸 단숨에 알 수 있다.

 

 

자바스크립트 엔진이 함수리터럴이든, 함수객체가 값으로 있는 메모리에 연결된 식별자든 여기에 소괄호를 붙여서 함수를 호출하면 이 함수는 실행이 되는것이다.

 

따라서 함수표현식으로 정의한 함수는 식별자에 소괄호를 붙여서 함수객체연결된식별자()이런식으로 호출하는것이기 때문에 변수호이스팅이 발생되는것이다.

 

 

따라서 위 코드

fuc(); // 타입에러 fuc은 함수가 아니래~

var fuc = function () {
  console.log("hello fuc");
};

var로 정의한 함수 fuc은 정의이전에 호출하려고하면 정확히 '변수호이스팅'이 발생하기 때문에, 초기화된 식별자 fuc을 참조해서 undefined를 가져오게 되고, fuc()이 undefined()으로 실행하려하기때문에 

요로코롬 타입에러. fuc이 함수가 아닌데 왜 호출하려고하냐? 뜨는거고

const로 정의한 함수는 fuc이 초기화가 안됐기때문에

참조에러부터 떠버린것이다.

 

 

 

 

 

 

# 함수 몸체내에서는 변수호이스팅이 발생할까?

fuc(); // value를 잘 찍어준다.

function fuc() {
  let value = "바보";
  console.log(value);
}

이렇게 함수선언문으로 정의한 함수 fuc에 변수선언과 참조를 해봤다.

호이스팅된 함수를 정의 이전에 호출했을때 이 함수몸체내에 변수선언/ 참조를 하였어도 이 변수선언/참조는 따로 문제없이 순서대로 잘 실행된다. 

 

fuc(); // 초기화에러뜹니당

function fuc() {
  console.log(value);
  let value = "바보";
}

위처럼 함수몸체내에서 변수호이스팅이 발생하게 한다면 

let으로 선언한 변수의 호이스팅 에러메세지인 초기화오류가 뜨게된다.

 

 

 

 

#  번외1 // 그렇다면 아래와 같은 코드는 어떻게 실행될까?

var value = "멍충이";

function fuc() {
  console.log(value);
  var value = "바보";
}

fuc(); // 어라? undefined가 뜬다?

위 코드에서 전역스코프에 value를 전역에서 선언해주었다. 여기엔 '멍충이'가 담겨있다.

그리고 함수 fuc몸체내부에선 value가 '바보'로 선언됐다. 그리고 참조를 선언보다 먼저하였다.

함수 fuc몸체내부에서 코드 순서로 봤을때는 console.log(value)에서 value식별자의 참조값은 전역에 선언한 value인 '멍충이'일것같다.

그러나 fuc()으로 함수호출을 했을때는 value='바보'가 담긴 선언문의 호이스팅된 결과인 undefined가 출력이된다.

왜일까?

아직 포스팅하지 않은 스코프에서 스코프체인이 발생해서 그렇다.

단순하게 설명하자면 스코프탐색은 가장 안쪽부터 탐색을 한다.

변수호이스팅의 범위가 함수스코프였기 때문에, value = '바보'인게 호이스팅된 초기화된 값인 undefined를 먼저 찾아서 출력하려고 한것이다.

 

 

 

# 그렇다면 함수를 한다면?

function fuc() {
  return "바깥쪽 함수";
}

function fucAll() {
  console.log(fuc());
  function fuc() {
    return "안쪽 함수";
  }
}

fucAll(); //안쪽함수 출력함

함수호이스팅도 함수스코프 범위로 된다.

 

 

 

 

# 블록에서 함수 정의의 범위는 어떻게 될까?

- 일반 블록문

{
  function fuc() {
    console.log("바보야!");
  }
}

fuc(); //바보래~!

var로 선언한 변수처럼 블록단위를 뛰어넘는 난봉꾼 함수 fuc이 탄생했다.

함수선언문으로 정의한 함수는 일반 블록문을 무시한다는걸 알 수 있다.

때문에 if문 for문 내에서 함수를 함수선언문으로 정의하는건 별로 좋은 선택이 아니다.

 

- 함수블록

function fucOut() {
  function fuc() {
    console.log("바보야!");
  }
}

fuc(); // fuc이 뭐야? 참조에러 뜹니다~

얜 참조에러 뜬다. 

함수선언문으로 정의한 함수더라도 넘사벽인 함수스코프는 뛰어넘질 못한다는걸 알 수 있다.

 

결과적으로 그냥 함수선언문으로 정의한 함수는 초기화따위는 없는 var로 선언한 변수랑 비슷하다.

 

 

+ 번외2 // 함수내에서 참조는 호출시점에 결정이 될까요?

const value = "바보";

function 밸류참조해줘함수1() {
  console.log(value);
}

function 밸류참조해줘함수2() {
  const value = "멍충이";
  밸류참조해줘함수1(); //밸류참조해줘함수1은 value를 콘솔로그로 참조하는건데 어떤 value를 참조할까?
}

밸류참조해줘함수1(); // 바보
밸류참조해줘함수2(); // 바보

위 코드에서

함수1은 value를 콘솔로 찍어줘~ 함수이다. 전역에 value가 있으니 함수를 호출하면 바로 '바보'를 콘솔로 찍어줄것이다.

쉽게 예상되쥬?

 

그렇다면 함수2는 어떨까?

함수2는 함수 몸체내에서 먼저 value를 '멍충이'로 선언할당해놨다. 그후 '밸류참조해줘함수1'을 호출했고 이 '밸류참조해줘함수1'은 value를 콘솔로 찍어주는 함수다. 그럼 여기서  '밸류참조해줘함수1'는 전역변수 value = '바보'를 가져올까? 아니면 지역변수 value = '멍충이'를 가져올까? 과연 어떤 value를 가져올까?

 

결과는 '바보'를 가져온다.

단순히 함수1을 value를 가져오는 행동이라고 봤을때는 '멍충이'를 가져왔겠지만, 다른결과다.

 

자바스크립트는 렉시컬 스코프를 따른다.

렉시컬 스코프에서 함수를 '어디에서 정의했는가'에 따라 상위스코프를 결정한다. 함수1은 value가 '바보'인 곳에서 정의를 했기때문에 '바보'인 value를 참조한것이다.

 

스코프 포스팅에서 쓸예정인데 걍 미리 좀 써봤다.