스코프와 호이스팅
03 객체지향 자바스크립트
스코프와 호이스팅

자바스크립트에서 스코프와 클로저는 중요한 개념입니다. 하지만 스코프와 클로저가 연결된 개념이다 보니, 헷갈리시거나 어려워하시는 분들이 많습니다. 클로저가 어려운 개념이기도 하고요.

따라서 이번 강의와 다음 강의를 통해 스코프와 클로저에 대해 설명하도록 하겠습니다. 그리고 추가로 자바스크립트의 중요한 개념 중 하나인 호이스팅에 대해서도 간단히 알아보겠습니다.


유효 범위(Scope)란?


scope의 사전적인 의미는 범위입니다. (따라서 스코프를 유효 범위라고 부르기도 합니다. 하지만 스코프라는 말이 더 널리 쓰이므로, 여기서도 스코프라 부르도록 하겠습니다.) 자바스크립트에서 스코프란 작성된 코드를 둘러싼 환경으로, 어떤 변수들에 접근할 수 있는지를 정의합니다. 어떤 범위 내에 속해있는지를 정의한다고 생각하면 사전적 의미와 유사하다고 할 수 있겠죠? 스코프는 전역(global)과 지역(local) 스코프로 정의할 수 있습니다.

전역 스코프는 함수 안에 포함되지 않은 곳에 정의하는 것으로 코드 어디에서든지 참조할 수 있고, 지역 스코프는 함수 내에 정의된 것으로 정의된 함수 내에서만 참조할 수 있습니다. 이 개념은 다른 프로그래밍 언어를 하신 적이 있는 분이라면 지역변수와 전역변수를 배울 때 비슷하게 들어보셨을 것입니다. 하지만 자바스크립트의 스코프는 다른 언어와 다른 특징을 가지고 있는데, 바로 자바스크립트는 Function-level scope(함수 레벨 스코프)를 사용한다는 것입니다.

대부분의 언어는 Block-level scope(블록 레벨 스코프)를 사용함으로써, 변수 선언이 코드 블록 단위로 유효합니다. 하지만 Function-level scope인 자바스크립트는 함수 블록 내에서 선언된 변수는 함수 블록 내에서만 유효하고 함수 외부에서는 참조할 수 없습니다. 

예를 들어 다른 프로그래밍 언어의 경우, 블록 단위이기 때문에 아래와 같이 if나 for 문에서 선언된 변수들은 그 중괄호 범위 밖에서는 사용할 수 없습니다. 

function foo() {
    if (true) {
        var a = 0; 
        console.log(a);     }     console.log(a); }

그러나 자바스크립트는 함수 레벨 스코프이기 때문에 위와 같은 상황일 때, 중괄호 밖에서 a를 출력하는 것이 가능합니다.

단, 자바스크립트 ES6부터는 const와 let을 이용해 블록 레벨 스코프도 지원하기 시작했습니다. 따라서 if 문 안에 var 대신 const나 let으로 변수를 선언하면, 다른 언어들처럼 참조하지 못합니다. const와 let은 블록 레벨 스코프, var와 같은 전통적인 자바스크립트의 변수는 함수 레벨 스코프라는 사실을 꼭 기억해두세요.


전역 스코프(Global scope)와 지역 스코프(Local scope)


일반적으로 쓰이듯이, 변수가 함수 바깥이나 중괄호{} 바깥에 선언되었다면, 전역 스코프에 정의되었다고 말합니다. 전역 스코프에 변수를 선언하면 코드 어디에서나 해당 변수를 사용할 수 있습니다. 당연히 함수 안에서도 가능합니다. 

지역 스코프는 함수 내의 범위로, 각각의 함수마다 자신의 지역 스코프를 가지고 있습니다. 어떤 함수 내의 지역 스코프에 선언된 변수가 있다면, 그 함수를 벗어난 범위에서는 그 변수를 참조할 수 없습니다. 따라서 당연하게도, 각각 선언된 함수가 있다면 서로의 스코프에 있는 변수는 참조할 수 없습니다. 간단한 예시 코드로 전역 스코프와 지역 스코프를 살펴보겠습니다.

실행 언어: js
실행 결과를 확인해보세요!

아마 함수를 벗어나면 쓰지 못하는 지역 스코프보다, 전역 스코프가 더 편하다고 생각하실 수도 있습니다. 하지만 되도록이면 전역 스코프에 변수 선언을 하지 않는 것이 좋습니다.

왜일까요? 그 이유는 변수의 이름이 충돌할 가능성이 있기 때문입니다. 

자바스크립트로 개발하면서 모든 것을 혼자 개발하지 않는 이상 다른 사람의 코드를 사용하거나, 팀원들과 함께 협업을 하는 일이 생깁니다. 그런데 전역 스코프에 변수를 선언하게 되면, 흔한 이름일 경우 겹치는 일이 생길 수 있겠지요? 이름이 겹치게 되면 이전에 있던 변수를 덮어 쓰게 될 수도 있습니다.

따라서 피치 못할 경우가 아닌 이상, 전역 스코프 대신 지역 스코프를 이용하는 것을 권장합니다. 


유효 범위 체인(Scope Chain)


자바스크립트는 위에서 말했듯이 함수 단위의 범위를 가지고 있습니다. 그래서 서로 다른 함수끼리는 참조할 수 없다고 했었는데요, 함수 안에 함수가 들어있는 경우에는 어떻게 될까요?

만약 아래 코드와 같이 함수를 정의하면, 제일 안쪽의 함수인 inner는 그 위쪽의 범위까지 흡수하게 됩니다. 이러한 메커니즘을 유효 범위 체인(Scope Chain) 이라고 일컫습니다. 흔히 스코프 체인이라고 말하죠. 다시 말해, 한 변수가 특정 함수 내부에서 정의되면 그 함수 밖에서는 존재하지 않는 것처럼 보이는 것입니다. 외부에서는 안에 있는 함수의 변수를 참조할 수 없지만, 안에 있는 함수에서는 외부의 변수를 사용할 수 있습니다.

실행 언어: js
실행 결과를 확인해보세요!

위의 코드를 살펴보면, a는 전역공간에서 선언되었기 때문에 함수 inner와 outer에서 사용할 수 있지만, 변수 c는 함수 내부 블록에서 선언되었기 때문에 함수의 밖인 전역범위에서는 인식하지 못합니다. outer 함수에서도 c 변수는 사용하지 못합니다. 하지만 inner 함수 안에서는 상위 스코프인 b 변수와 a 변수 모두 사용할 수 있습니다.

inner 함수에서는 a변수를 참조할 때 먼저 자기 자신의 스코프에서 a를 찾고, 없으면 상위 스코프인 outer 함수의 스코프에서 a를 찾고, 거기에도 없으면 또다시 상위 스코프인 전역 범위 G로 올라가 a를 찾습니다. 최종적으로 전역 스코프에도 찾는 변수가 없으면 not defined 에러를 출력합니다. 이렇게 계속해서 체인처럼 꼬리에 꼬리를 물고 상위 스코프를 참조하기 때문에 스코프 체인이라고 이름이 붙여졌습니다.

다음 그림은 위의 예제 코드를 그림으로 나타낸 것으로서 함수 inner는 a를 참조할 수 있지만 전역 범위 G에서는 함수 inner의 내부에 있는 c를 참조할 수 없습니다. 


정적 범위(Lexical scope)


이제 렉시컬 스코프에 대해 알아볼까요? 렉시컬 스코프는 전역 스코프와 지역 스코프처럼 기본적인 자바스크립트의 특징입니다.

lexical의 사전적 의미는 "어휘의"라는 의미이지만, "어휘 범위"라고 하면 무슨 뜻인지 잘 와닿지 않습니다. 따라서 렉시컬 스코프를 번역할 때는 흔히 "정적 범위", "정적 스코프"라고 번역합니다. 렉시컬 스코프란, 함수를 어디서 호출하는지가 아니라 어떤 스코프에 선언하였는지에 따라 결정된다는 것입니다. 이렇게 말하면 무슨 뜻인지 잘 이해가 되지 않죠? 예시 코드를 보면서 얘기해보도록 하겠습니다. 실행버튼을 눌러서 답을 확인하기 전에, 어떤 것이 출력될 것인지 아래 설명을 읽기 전에 생각해보세요.

실행 언어: js
실행 결과를 확인해보세요!

text 변수가 "global"에서 "bar"로 바뀌었으니, 당연히 "bar"가 출력될 것 같지 않으신가요?

하지만 실행하면 "global"이 출력됩니다. foo에서 출력한 text는 bar 함수의 지역 변수 text가 아니라 전역 변수 text를 가르키고 있기 때문입니다. 위에 설명했던 스코프 체인과도 연관이 있는 개념인데요, 먼저 foo 함수에서 text를 참조할 때 자기 자신의 스코프에서 text를 먼저 찾아보고, 없기 때문에 상위 스코프인 전역 스코프에서 text를 찾아서 출력하게 됩니다. 여기까지는 이해가 금방 되실 겁니다.

그런데 문제는, bar 함수에서 foo 함수를 불러온다는 겁니다. 그렇다면 foo 함수는 text를 참조해야 하니까, 상위 스코프인 bar 함수에서 text를 찾을 거라고 생각할 수 있지만, 아까 위에서 얘기했듯이 "어디서 호출하는지가 아니라 처음 선언되었을 때에 어떤 스코프에 있는지"가 중요합니다. 즉, 스코프란 코드를 실행하면서 바뀌는 것이 아니라 처음 작성한 그 스코프로 결정된다는 것입니다.

따라서 foo는 bar에서 호출되든 어떤 함수 안에서 호출되든지 상관없이, 무조건 자기 자신의 스코프를 찾아보고 그 이후에는 전역 스코프를 찾는다는 것입니다. 이제 조금 이해가 되시나요? 이렇게 foo가 한번 선언된 이상 전역변수 text를 참조하는 것을 바꿀 수 없습니다. 만약 text를 bar로 바꾼 후에 출력하고 싶다면, 지역변수 var를 선언할 것이 아니라 전역변수 var의 값을 바꾸면 됩니다. 이렇게요.

실행 언어: js
실행 결과를 확인해보세요!


호이스팅(hoisting)


hoisting의 사전적 의미는 끌어 올리기라는 뜻입니다. 호이스팅도 자바스크립트의 특징 중 하나인데, 말 그대로 함수 안에서 변수를 선언할 때 어떤 위치에 있든 함수의 시작 위치로 끌어올리는 현상입니다. 단, 선언 부분만 위로 끌어올리고 값을 대입하는 부분은 위치 그대로 남아있습니다. 예시 코드를 통해 좀 더 자세히 알아보겠습니다. 

실행 언어: js
실행 결과를 확인해보세요!
다른 프로그래밍 언어의 경우 위의 코드를 실행하면 a가 선언되지 않았는데 a를 호출했기 때문에 에러가 발생합니다. 하지만 자바스크립트의 경우는 호이스팅을 통해 a의 선언을 함수 제일 위에서 해주기 때문에, 에러 없이 undefined가 출력됩니다. 위 코드는 사실 아래와 같은 코드입니다.
실행 언어: js
실행 결과를 확인해보세요!
변수 호이스팅이 어떻게 작동되는지는 이해가 가시죠? 함수 호이스팅도 어떻게 작동하는지 살펴보도록 하겠습니다. 
실행 언어: js
실행 결과를 확인해보세요!

위와 같은 코드의 경우, 변수 호이스팅과 마찬가지로 함수선언이 위로 끌어올려지기 때문에 제대로 동작합니다. 하지만 아래와 같은 함수 표현식의 경우에는 오류가 발생합니다.

실행 언어: js
실행 결과를 확인해보세요!
왜일까요? 사실 위의 코드는 아래와 같기 때문입니다. 
실행 언어: js
실행 결과를 확인해보세요!

위와 같이 foo 선언을 위로 호이스팅 해버리기 때문에, foo가 실행될 때는 아직 변수로 선언이 된 상태일 뿐인 것입니다. 따라서 foo는 함수가 아니라는 에러 메세지를 보게 됩니다.

이 호이스팅은 혼란스러울 수 있기 때문에, 함수를 호출하기 전에 최상단에 선언하는 습관을 들여야 합니다.

질문하기
추가 자료
no files uploaded

추가 자료가 없습니다

여기서 새로운 학습 자료를 확인하세요!
선생님이 추가한 자료들을 바로 확인할 수 있어요.