스코프, 호이스팅

Loopy·2023년 6월 4일
2

스코프

모든 식별자는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정되는데, 이때의 유효범위를 스코프라고 한다.

또 다른 말로 JS엔진이 식별자를 검색할 때 사용하는 규칙이라고도 할 수 있다.

그 규칙은 무엇일까?

📌 첫 번째는 스코프 내에서 식별자는 유일해야 한다는 규칙이다.
하지만 다른 스코프에는 같은 이름의 식별자를 사용할 수 있다. 이것은 스코프가 네임스페이스라는 것을 말한다.

즉 아래와 같은 상황을 말한다. 전역 범위 영역과 foo함수 내부에 같은 이름을 갖는 a 변수가 있고 (1)과 (2)에서 a 변수들을 참조하는 상황이다.

var a = 'hello';

function foo() {
	var a = 'world';
	console.log(a); // (1)
}

foo();
console.log(a); //(2)

📌 두번째는 변수를 참조할 때 JS 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작해 상위 스코프 방향으로 이동하며 변수를 검색하는 규칙이다.

그렇다면 스코프 체인이 무엇일까?

스코프 체인은 스코프를 계층적으로 연결된 것을 말한다. 모던 JS 딥다이브 책에서는 쉽게 그림을 통해 보여준다.

var x = 'global x';
var y = 'global y';

function outer(){
	var z = 'outer's local z';
	
	console.log(x);
	console.log(y);
	console.log(z);

	function inner(){
	var x = "inner's local x';
	
	console.log(x);
	console.log(y);
	console.log(z);
	}
	inner();
}

outer();

console.log(x);
console.log(z);


함수레벨 스코프 & 블록 레벨 스코프

Javascript 공부를 하다보면 자연스럽게 var 키워드는 함수 레벨 스코프, let과 const 키워드는 블록 레벨 스코프 이렇게 암기하게 된다. 그게 어떤 원리를 가진 것인지 이해하기 위해 따로 정리해보았다.

대부분의 프로그래밍 언어는 함수 몸체만이 아니라 모든 코드 블록( if, for, while 등등)이 지역 스코프를 만드는데 이를 블록 레벨 스코프라고 한다. 그렇다면 함수 레벨 스코프는 무엇일까?

var 키워드의 함수 레벨 스코프는 즉 오로지 함수의 코드 블록만을 지역 스코프로 인정한다는 것이다. 밑의 예제를 보면 이해하기 쉽다.

var x = 1;
if(true){
	var x = 10;
}

console.log(x);

이 예제를 콘솔로 찍어보면 1이 아닌 10의 값이 나온다.
var 키워드는 함수 레벨 스코프만을 인정하기 때문에 코드 블록(if)내에 선언되었을 지라도 전역 변수가 된다. 따라서 전역변수 x 는 중복 선언되고 그 결과는 1이 아닌 10이 되는것이다.

이렇게 의도하지 않은 결과를 내는 경우가 많기 때문에 var가 함수 레벨 스코프라는 점은 var를 권장하지 않는 이유 중 하나이다.

❓ 그렇다면 var를 권장하지 않는 이유가 또 있을까?

함수 레벨 스코프 특성 말고도 호이스팅에 의해 var를 권장하지 않는 이유도 있다.

호이스팅은 쉽게 말하면 변수 선언문이 스코프의 선두로 끌어 올려지는 것을 말한다. 즉 변수 선언문 이전에 참조가 가능한 것이다.

console.log(foo); //undefined

foo = 'hi';

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

위 예제를 보면 코드 가장 밑에 변수 선언문이 있다. 호이스팅의 과정은 밑의 순서와 같다.

  1. 먼저 변수 선언은 런타임(실행시간) 이전에 JS 엔진에 의해 암묵적으로 실행된다.
  2. 그 후 호이스팅에 의해 코드 맨위에 foo변수가 선언된 것 처럼 동작한다.
  3. 코드 선두에서 변수 foo는 호이스팅에 의해 undefined로 초기화된다. 그래서 첫번째 콘솔값은 undefined가 나온다.

이렇게 변수 선언문 이전에 변수를 참조하는 것이 호이스팅에 의해 에러를 일으키진 않지만 개발자의 의도와 다르게 사용 될수 있으며 가독성을 떨어트리고 버그를 발생시킬 수 있다.

(정리) ❗var를 권장하지 않는 이유

  • 함수 레벨 스코프라는 특성이 의도지 않은 결과를 만들어 에러를 일으킬 수 있다.
  • 호이스팅으로 인해 선언 이전에 참조가 가능하여 참조에러가 발생하지는 않지만 undefined를 반환하여 오류들이 일어날 수 있다.
  • 중복 선언이 가능해 의도치 않게 변수 값이 변경될 수 있다.

let, const, 호이스팅

위에 var 키워드를 권장하지 않는 이유들을 설명해보았는데 이를 보완하기 위해 ES6에서는 새로운 변수 키워드 let과 const를 제공했다.

기존의 var 키워드와 비교해보면 다음과 같다.

let

  • 중복선언 금지
  • 블록 레벨 스코프
  • 호이스팅 진행 방식 차이

const

  • 중복선언, 재할당 금지
  • 블록 레벨 스코프
  • 상수 선언(선언과 동시에 초기화 필수)
  • 호이스팅 진행 방식 차이

❓ 그렇다면 const 키워드는 무조건 값을 변경할 수 없나요?

NO ❌❌

const 키워드에 원시 값이 할당된 경우는 변경할 수 없다!!!

하지만 객체 타입이 할당된 경우에는 값을 변경할 수 있다!! 즉 아래와 같은 예제를 말한다. 변경이 가능한 이유는 메모리와 관련해서 이해해야 하는데 전에 작성한 글인 TIL DAY2의 4번 내용을 참고하면 된다.

const panda = {
	name: '푸바오';
};

panda.name = '아이바오';

console.log(panda); //{name: '아이바오'}

let,const의 호이스팅

위 var 키워드의 호이스팅 예시를 보면 런타임 이전에 JS엔진에 의해 암묵적으로 선언단계와 초기화단계(undefined)가 한번에 진행된다.

하지만 let과 const 키워드로 선언한 변수는 선언단계와 초기화 단계가 분리되어 진행된다. 그래서 초기화 이전에 변수에 접근하면 참조에러 ReferenceError가 발생한다. 밑 예제를 보면 쉽게 알 수 있다.

console.log(x); // ReferenceError

let x; //초기화 단계
console.log(x); // undefined

x = 'hi';
console.log(x); // hi
  1. 런타임 이전에 코드 맨 위에 선언 단계가 실행된다. 초기화는 X

  2. 첫줄의 console.log(x);에서 초기화 단계 전에 변수에 참조하려 했으므로 ReferenceError가 발생한다.

    👉 이렇게 스코프 시작 지점부터 초기화 단계 전까지 변수를 참조할 수 없는 구간을 TDZ 일시적 사각지대라고 한다.

  3. 이후 let x; 에서 초기화 단계가 실행되 undefined가 출력된다

  4. x = ‘hi’; 할당 단계가 실행되면서 hi값이 출력된다.



함수의 호이스팅

함수의 호이스팅은 함수가 어떻게 정의되는지에 따라 호이스팅 결과가 다르다

  1. 함수 선언식

    greeting(); //hello
    
    function greeting(){
    	console.log('hello');
    }

    함수 선언문으로 정의한 함수의 호이스팅의 경우 함수 선언문이 코드의 선두로 올려진것처럼 동작해 hello가 출력된다.

  2. 함수 표현식

    greeting(); // greeting is not a function
    
    var greeting = function (){
    	console.log('hello');
    }

    함수 표현식으로 정의한 함수의 호이스팅은 위에 적은 var,let,const의 변수 호이스팅과 같은 방식으로 동작한다. 즉 위 예제의 경우 var 변수 선언은 런타임 이전에 실행되어 undefined로 초기화되고 타입에러가 발생한다.



참고자료

2개의 댓글

comment-user-thumbnail
2023년 6월 4일

이해가 잘돼요 잘 봤습니다 👍

답글 달기
comment-user-thumbnail
2023년 6월 6일

함수 레벨 스코프 예시 덕에 한 눈에 이해했습니다! 감사합니다!

답글 달기