CodeStates/JavaScript

[JavaScript] 호이스팅

디스페어 2022. 1. 21.

Scope (유효범위)

호이스팅에 대해 알기 위해선 먼저 전역과 지역 스코프가 무엇인지 알아야 된다.

  • 함수와 변수는 전역 또는 코드 블록, 함수 내에 선언되며, 선언된 위치에 따라 유효범위를 갖는다
    어디에 선언되었는지에 따라 다른 코드가 자신을 참조할 수 있는지 결정됨

 

 

스코프의 범위

자바스크립트에서 스코프는 전역 스코프(Global)와 지역 스코프(Local Scope)로 나뉜다.

 

1. 전역 스코프

  • 가장 바깥쪽 스코프
    전역 스코프에 선언된 변수는 전역 변수
  • var 키워드로 선언된 전역 변수는 전역 스코프에 저장되어 console.log(window.변수명)으로 접근 가능
  • let, const 키워드로 선언된 전역 변수는 전역 스코프에 저장되지 않고 스크립트 스코프에 저장된다
var a = 1
let b = 2
const c = 3
//위의 키워드들의 스코프를 보면 아래와 같다
Script
  b: 2
  c: 3
Global
  a: 1

 

2. 지역 스코프

  • 지역 스코프에 선언된 변수는 지역 변수
  • 지역 스코프를 빠져 나오면 어떤 키워드(var, let, const)로 선언되었든 해당 지역 스코프의 지역 변수는 삭제 된다
    이미 삭제 되었기 때문에 지역 스코프 밖에서 안으로 값을 불러올 수 없음
    쉽게 안에선 밖을 볼 수 있고 밖에선 안을 볼 수 없는 거울유리를 생각하면 됨

 

3. 함수레벨 스코프

  • 함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다
    함수 내부에서 선언한 변수는 지역 변수이며, 함수 외부에서 선언한 변수는 모두 전역 변수

 

4. 블록레벨 스코프

  • 모든 코드 블록에서 선언된 변수는 코드블록 내에서만 유효하며, 코드 블록 외부에서는 참조할 수 없다
    함수, if문, for문 while문, try/catch문... 등등
  • 코드 블록 내부에서 선언한 변수는 지역변수

 

 

var 키워드 특징

ES6 이후부터는 아래와 같은 이유로 let과 const 사용을 권장한다

 

1. 블록레벨 스코프를 무시하며, 함수레벨 스코프만 따른다

  • 블록레벨 스코프를 무시하며, 함수레벨 스코프만 따른다
  • for문의 변수 선언문에서 선언한 변수를 for문 코드 블록 외부에서 참조할 수 있음
for(var i=0;i<5;i++)
{
  console.log(i); //0,1,2,3,4
}
console.log(i); //5

//var는 블록레벨 스코프를 무시한다. 
//var i=0으로 선언된 변수 i는 블록코드 내에서만 유효한 게 아니라 전역변수처럼 사용된다. 
//따라서 다음과 같이 코드블록 밖의 콘솔창에서 i를 출력해도 reference error를 출력하지 않는다.

 

2. var 키워드 생략을 허용한다

  • 전역변수와 같은 이름의 변수를 함수레벨 스코프 안에 var 키워드 없이 선언할 경우엔
    새로운 변수를 선언하여 할당하는 것이 아니라, 전역 변수 값을 재할당함
i = 3;
function show(){
  i = 5;
  console.log(i); //5
}
show();
console.log(i); //5

위의 코드 실행 순서는 다음과 같다.
1. 1번째 줄에 의해 전역객체 윈도우에 전역변수 i=3 할당
2. 6번째 줄에 의해 show()를 실행시킨다
   2-1. 3번째 줄에 의해 전역변수 i=5로 재할당
   2-2. 4번째 줄에 의해 콘솔창에 5 출력하고 함수 종료
3. 7번째 줄에 의해 전역객체에 저장된 i=5 출력

 

3. 변수 중복 선언 가능

  • 변수 중복 선언이 허용되면 기존에 선언한 값이 덮어씌워지기 때문에 코드 전체에 큰 문제를 일으킬 수 있음
    let과 const는 재선언이 불가능 → Uncaught SyntaxError: Identifier '...' has already been declared 오류 출력

 

4. 호이스팅

  • 선언문을 해당 스코프의 선두로 옮긴 것 처럼 동작하는 특성이 있음
    자바스크립트는 var, let, const, function class 등의 선언을 호이스팅한다
a = 1;
console.log(a); //1
var a;
//변수 a는 호이스팅되었기 때문에 1을 출력한다

console.log(b); //undefined
var b;

console.log(c); // Error: Uncaught ReferenceError: bar is not defined
let bar;
//let 키워드로 선언된 변수를 선언문 이전에 참조하면 참조에러(ReferenceError)가 발생

 

 

let & const

  • let & const 키워드는 재선언 시 Uncaught SyntaxError: Identifier '변수명' has already been declared 에러 발생
  • const 키워드는 재할당 시 Uncaught TypeError: Assignment to constant variable. 에러 발생
  • let & const 키워드는 호이스팅이 되지만, 선언과 초기화 단계 사이에 TDZ(Temporal Dead Zone)가 존재함
  재선언 (중복선언) 재할당 호이스팅
var 가능 가능 가능
let 불가능 가능 가능
const 불가능 불가능 가능

 

 

호이스팅 과정

1. 선언 declaration phase

  변수를 실행 컨택스트의 변수 객체에 등록하며, 이 변수객체는 스코프가 참조하는 대상이 된다

 

2. 초기화 Initialization phase
  변수 객체에 등록된 변수를 위한 공간을 메모리에 확보하며, 이 단계에서 변수는 undefined로 초기화 된다

 

3. 할당 Assignment phase
  undefined로 초기화된 변수에 실제 값을 할당한다

 

1. var 키워드로 선언

  • 선언 단계와 초기화 단계가 한번에 이루어짐
    스코프를 변수에 등록(선언)하고 메모리에 변수를 위한 공간을 확보한 후, undefined로 초기화한다
    변수 선언문 이전에 변수에 접근해도 스코프에 이미 변수가 존재하므로 undefined를 반환
  • 변수 할당문에 도달하면 값이 할당됨
  선언단계 초기화 단계 foo === undefined
var foo;      
foo = 1; 할당 단계 foo === 1
var 키워드로 선언된 변수의 생명 주기
// 스코프의 선두에서 선언 단계와 초기화 단계가 실행된다.
// 따라서 변수 선언문 이전에 변수를 참조할 수 있다.
console.log(foo); // undefined

var foo;
console.log(foo); // undefined

foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1

 

2. let 키워드로 선언

  • 선언과 초기화가 분리되어 진행
    스코프에 변수를 등록하지만 코드블록의 선두부터 초기화가 이루어지는 지점까지 일시적 사각지대(TDZ)가 존재함
  • 초기화는 변수 선언문에 도달했을때 이루어짐
  선언단계 ReferenceError
let foo; 일시적 사각지대
foo = 1; 초기화 단계 foo === undefined
  할당 단계 foo === 1


let 키워드로 선언된 변수의 생명 주기

// 스코프의 선두에서 선언 단계가 실행된다.
// 아직 변수가 초기화(메모리 공간 확보와 undefined로 초기화)되지 않았다.
// 따라서 변수 선언문 이전에 변수를 참조할 수 없다.
console.log(foo); // ReferenceError: foo is not defined

let foo; // 변수 선언문에서 초기화 단계가 실행된다.
console.log(foo); // undefined

foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1
let foo = 1; // 전역 변수

{
  console.log(foo); // ReferenceError: foo is not defined
  let foo = 2; // 지역 변수
}

위 예제의 경우, 전역 변수 foo의 값이 출력될 것처럼 보인다. 
하지만 ES6의 선언문도 여전히 호이스팅이 발생하기 때문에 참조 에러(ReferenceError)가 발생한다.

//ES6의 let으로 선언된 변수는 블록 레벨 스코프를 가지므로 코드 블록 내에서 선언된 변수 foo는 지역 변수이다.
//따라서 지역 변수 foo도 해당 스코프에서 호이스팅되고 코드 블록의 선두부터 초기화가 이루어지는 지점까지 일시적 사각지대(TDZ)에 빠진다. 
//따라서 전역 변수 foo의 값이 출력되지 않고 참조 에러(ReferenceError)가 발생한다.
let foo = 1;
{
  console.log(foo); //Uncaught ReferenceError: Cannot access 'foo' before initialization
  let foo = 2;
}

//foo가 전역 변수로 선언되어있기 때문에 세번째 줄에서 1을 출력할 것 같지만 참조 에러를 출력한다.
//블록레벨 스코프안 foo는 지역변수이므로 지역변수 foo도 해당 스코프에서 호이스팅 되며,
//초기화 되는 시점까지 일시적 사각지대에 빠진다 → 초기화는 변수 선언문에 도달했을때 이루어진다.
//즉, 전역변수에 같은 이름이 변수로 할당되어 있더라도 블록레벨 스코프 안의 지역변수에 대해서
//호이스팅이 또 다시 일어나므로, 변수를 먼저 할당해주어야 한다.

 

3. const 키워드로 선언

  • 선언과 할당 구문을 한번에 작성해야함
const a;
//Uncaught SyntaxError: Missing initializer in const declaration
//선언만 할 경우 위와 같은 SyntaxError 에러 발생

 

4. 익명함수

  • 익명함수는 호이스팅 되지 않는다
  • 기명함수는 스크립트 어느 곳에 선언되더라도 호이스팅되어 함수를 호출할 수 있다
  • 호이스팅 되는 것이 많을 수록 페이지가 렌더링 되기 전에 브라우저에서 사용하는 메모리가 많아진다
    함수 내에서 사용하는 변수는 함수레벨 스코프에서 선언하고, 익명함수를 사용해 호이스팅을 막을 수 있다
console.log(example()); //10
function example(){
    const a = 10;
    return a;
}

console.log(func()); //Uncaught ReferenceError: func is not defined
const func = function(){
    const b = 10;
    return b;
}

console.log(func()); //Uncaught ReferenceError: func is not defined
const func = () => {
    const c = 10;
    return c;
}

 

 

Reference

let, const와 블록 레벨 스코프

반응형

'CodeStates > JavaScript' 카테고리의 다른 글

[JavaScript] 고차함수  (0) 2022.02.27
[JavaScript] 원시 자료형과 참조 자료형  (0) 2022.02.09
[JavaScript] 배열과 객체  (0) 2022.01.04
[JavaScript] 반복문  (0) 2021.12.19
[JavaScript] 문자열  (0) 2021.12.19

댓글