자바스크립트 Scope와 Hoisting 개념 이해

안녕하세요.

오늘은 자바스크립트 초급자를 벗어나기 위해 필수적으로 이해가 필요한 Scope와 Hoisting에 대해 정리해 보고자 합니다. :)

목차

Contents

  • What is Scope?
  • Lexical Scope
  • Execution Context
  • Scope Chaining
  • 호이스팅

What is Scope?

A scope is the part of a program where a binding between a variable and value is valid. Variables are only available under certain circumstances. Different languages have different access rules.

스코프란 프로그램의 한 부분에서 접근 가능한 변수와 그 변수에 묶이는 값의 유효 범위이다.

스코프에는 Global scope(전역변수)와 Local scope(지역변수)로 구분되어 있습니다. 자바스크립트에서 이렇게 스코프가 구분된 이유는 무엇일까요?

저는 프로그램이 커져 코드 양이 많아지고, 여러 개발자가 함께 작업할 때 오류를 줄이기 위해서가 아닐까 하고 생각합니다. 만약 모두 전역변수를 사용한다면, 오류가 발생했을 때 코드 전체를 뒤져봐야 하겠지만, 지역변수를 사용한다면 검토해야 하는 범위를 많이 줄일 수 있기 때문입니다.

Lexical Scope

Scope라는 용어는 여러 뜻으로 쓰일 수 있습니다. 일반적으로 단순히 Scope라고 하면 Lexical Scope를 뜻하는 경우가 많습니다. Lexical Scope는 에러가 발생하지 않고 접근 가능한 변수의 범위를 말합니다. Lexical Scope는 코드를 실행하지 않고도 확인할 수 있습니다. Lexical Scope는 변수가 쓰여진 장소만 고려하면 되기 때문입니다.

Lexical Scope는 위로 새로이 형성됩니다. 함수 안에서는 함수 바디 내 변수와 함수 밖 Global 영역의 변수에 접근할 수 있지만, 함수 밖에서는 함수 안의 변수에 접근할 수 없습니다. 중괄호나 if 문의 블락, 반복문의 블락은 자바스크립트에서 별도의 스코프를 생성하지 않습니다. 하지만, ES6에서 도입된 let과 const는 블락 스코프를 형성합니다.

아래의 코드는 가상의 히어로를 만들고, 주어 동사로 문장을 만들어 스토리를 이어가는 프로그램입니다. 아래의 간단한 프로그램으로 스코프의 개념에 대해 학습하겠습니다.

// Scope 개념 예시 - 1
var hero = 'Ironman';

var newSaga = function() {
  var what = ' cars';

  var saga = function() {
    var deed = ' loves';
    console.log(hero + deed + what);
  };

  saga();
};

newSaga(); // -> 'Ironman loves cars'

console.log(hero); // -> Ironman
console.log(what); // -> Uncaught ReferenceError
console.log(deed); // -> Uncaught ReferenceError

앞서 언급한 바와 같이, scope는 함수 단위로 형성됩니다. 함수 newSaga를 호출하면 콘솔 창에 'Ironman loves cars'이 출력됩니다. 하지만 함수 밖에서 호출 시 위와 같이 ReferenceError가 발생합니다.


예제를 많이 보고 언어에 익숙해 지면 좋으니, 아래의 다른 코드를 살펴보겠습니다.

// Scope 개념 예시 -2
var name = 'Richard';

if (name) {
  name = 'Jack'; // if문의 name 변수의 지역 변수를 생성하지 않고, 젼역 변수의 값을 수정함.
  console.log(name); // Jack이 출력됨.
}

console.log(name); // Jack

그리고, 지역변수는 함수 내에서 전역변수보다 우선순위가 높습니다. 만약 Global Scope에 어떠한 변수가 선언되어 있고, 함수 내 같은 변수명으로 변수가 선언되어 있다면, 이 함수 내부에서는 지역 변수에 위치한 변수가 우선시됩니다.

var name = 'Paul';

function users() {
  var name = 'Jack';
  console.log(name); //
}

console.log(users()); // Jack이 출력됨.
console.log(name); // Paul이 출력됨

변수를 사용할 때에는 항상 주의해야 합니다. 사용하려는 변수가 최초로 선언되어야 하는지 인지하는 것은 프로그래밍에서 오류 방지를 위해 중요하다고 생각합니다. 만약, 변수가 최초 선언 없이 함수 내부에서 선언된다면, 이 변수는 자동으로 전역변수에 추가됩니다.

function showAge() {
  age = 90;  // 변수를 선언하지 않고 값을 할당했으며, age는 이제 전역 변수가 됨.
  console.log(age);
}

ShowAge() // 90이 출력

console.log(age); // 90이 출력. 이러한 문제로 조심해야 함!!

위와 같이 잠재적 오류 방지를 위해, 꼭 필요한 경우에만 전역범위에 변수를 생성하는 것이 좋습니다.


Execution Context

Scope란 용어는 Lexical Scope와 다른 의미를 나타내는 데도 사용합니다. 프로그램이 동작하고 있을 때 변수와 그 값을 간직하고 있는 별도의 메모리 영역이 생성합니다. 이 별도의 In-Memory Scope를 Execution Context라고 합니다.

Execution Context는 코드가 쓰인 위치에 따라서가 아니라, 실행되며 생성되는 점에서 Lexical Scope와는 다릅니다. Execution Context 관련해서는 아래 별도의 포스팅을 참고해 주세요.

https://chanwhe-kim.netlify.app/blog/executioncontext/


스코프 체이닝?

스코프 체이닝이란, 스코프는 중첩될 수 있음을 의미합니다. 기본적으로 스코프는 함수 단위로 나뉩니다. 함수 안의 또 다른 내부 함수가 있을 수 있고, 이 내부 함수는 외부 함수의 변수에 접근할 수 있습니다. ES6 문법에서는 블록 레벨 스코프가 도입되었지만, 이 포스팅은 일단은 ES5 기준으로 생각하고자 합니다.


호이스팅

호이스팅이란 범위에 따라 변수를 선언과 할당으로 분리하는 것을 의미합니다. 조금 풀어서 말하자면, 자바스크립트 컴파일러가 내부적으로 변수 선언을 Scope의 상단으로 끌어올려 주는 것을 말합니다. 코드를 보며 이해하는 것이 말로 풀어쓴 설명보다 항상 더 쉬운 것 같습니다. 몇 가지 예를 살펴보겠습니다.

변수에 값을 아래와 같이 할당하면, 첫 번째 name은 undefined, 두 번째 name은 값이 할당돼서 'Ford'가 출력됩니다.

function showName() {
  var name;
  console.log('First name : ' + name); // First name : undefined
  name = 'Ford';
  console.log('Last Name : ' + name); // Last Name : Ford
}

다음은 호이스팅의 간단한 예입니다.

a = 2;
var a;
console.log(a); // 2를 출력

자바스크립트 Interpreter는 코드를 두 번 읽습니다. 먼저 선언된 변수를 읽어 들이고, 두 번째 읽을 때 값을 할당해 줍니다. 따라서 위의 코드도 var a가 먼저 선언이 되고, 그다음에 값이 할당되기 때문에 console.log(a) 실행 시 2가 출력됩니다.

호이스팅의 또 다른 예를 살펴보겠습니다.

console.log(a);
var a = 2;

//   ↓

// 실제 자바스크립트 엔진은 위의 코드를 아래와 같이 읽습니다.
var a;
console.log(a); // reference error가 아닌 undefined가 출력됨.
a = 2;

같은 맥락이지만, 아래와 같이 변수의 값으로 함수를 할당하고, 변수에 값이 할당되기 전에 그 함수를 호출하게 되면 TypeError가 발생합니다.

이 메시지는 해당 변수가 값이 undefined인데, 함수로 호출했다고 저희에게 알려주는 것입니다.

show(); // TypeError
var show = function() {};