자바스크립트의 변수범위와 호이스팅

이 글에서, 우리는 자바스크립트 변수의 범위(scope)와 호이스팅(hoisting), 그리고 이 두가지의 특징에 관해 배워보겠습니다.

scope image

자바스크립트의 변수범위와 호이스팅이 작동하는 원리를 이해하는것은 필수적입니다. 이 두가지 컨셉은 직관적이면서도 이해하기가 쉽지 않습니다. 거기에는 미묘한 차이가 있으며, 자바스크립트 프로젝트에서 성공하기 위해서는 반드시 이해해야 합니다.

변수 범위 (Variable Scope)

변수 범위는 변수가 존재하는 컨텍스트입니다. 이것은 어디에서 변수에 접근할 수 있는지, 그 컨텍스트에서 변수에 접근할 수 있는지를 명시적으로 나타납니다.

변수는 지역 범위(local scope)와 전역 범위(global scope) 둘 중 하나를 가집니다.

지역 변수 (함수 수준 범위)

대부분의 프로그래밍 언어와 달리, 자바스크립트는 블럭-수준(block-level)의 범위를 가지고 있지 않습니다. 대신, 자바스크립트는 함수-수준(function-level)의 범위를 가집니다. 함수내에 정의된 변수는 지역 범위를 가지며, 해당 함수와 내부 함수에서만 접근이 가능합니다. 내부 함수에서 외부 함수의 변수 접근에 관한 더 자세한 내용은 클로저(Closure)를 설명한 글을 참조하시기 바랍니다.

함수-수준 범위의 예제

1
2
3
4
5
6
7
var name = "Richard";
function showName() {
var name = "Jack"; // 지역 변수; showName()함수에서만 접근가능.
console.log(name); // Jack
}
console.log(name); // Richard : 전역 변수

잘못된 예제. (블럭-수준 범위로 오해할 경우)

1
2
3
4
5
6
7
8
var name = "Richard";
// 아래의 if문은 name변수에 대한 지역-범위를 생성하지 않습니다.
if (name) {
name = "Jack";
console.log(name); // Jack : 전역 변수
}
// name은 여전히 전역변수이며 if문에서 변경되었습니다.
console.log(name); // Jack

지역변수를 선언하지 않는다면 문제를 일으킬 가능성이 높아집니다.

항상 지역변수를 사용하기 이전에 선언하도록 하십시오. JSHint를 사용하면 코드의 문법 오류나 스타일을 체크할 수 있습니다. 다음은 지역변수를 선언하지 않음으로 인해 문제가 발생한 경우입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 지역변수를 var키워드로 선언하지 않았을 경우, 그것은 전역-범위(global-scope)가 됩니다.
var name = "Michael Jackson";
function showCelebrityName() {
console.log(name);
}
function showOrdinaryPersonName() {
name = "Johnny Evers";
console.log(name);
}
showCelebrityName(); // Michael Jackson
// name 은 지역변수가 아닙니다. 이것은 전역변수 name을 변경해 버립니다.
showOrdinaryPersonName(); // Johnny Evers
// 이제 전역변수 name은 Johny Evers입니다. 더이상, 셀럽의 이름은 없습니다. -.-;;
showCelebrityName(); // Johnny Evers
// 해결책은 지역변수 선언시 var 키워드를 사용하는 것입니다.
function showOrdinaryPersonName() {
var name = "Johnny Evers"; // 이제 name은 항상 지역변수이며, 전역변수를 덮어쓰지 않습니다.
console.log(name);
}

지역번수는 함수내에서 전역번수보다 높은 우선순위를 가집니다.

만약, 같은 이름의 전역변수와 지역변수가 존재할 경우 이 변수를 함수내에서 사용한다면, 지역변수가 우선권을 갖게 됩니다.

1
2
3
4
5
6
var name = "Paul";
function users() {
var name = "Jack";
console.log(name);
}
users(); // Jack

전역 변수

함수의 외부에서 선언된 모든 변수는 전역 범위(global scope)를 가집니다. 브라우저에서, 전역 컨텍스트(또는 scope)는 window 객체를 가리킵니다.

그러므로, 전역변수는 전체 어플리케이션에서 사용이 가능합니다.

1
2
3
4
5
6
7
// 전역변수는 아래와 같이 선언될 수 있습니다.
var myName = "Richard";
// 또는
firstName = "Richard";
// 또는
var name;
name;

모든, 전역 변수는 window객체와 연결됩니다. 그러므로, 아래와 같이 window객체를 통해 모든 전역 변수에 접근이 가능합니다.

1
2
3
4
console.log(window.myName); // Richard
// 또는
console.log("myName" in window); // true
console.log("firstName" in window); // true

만약, 변수가 최초 선언 없이(var 키워드를 사용하여) 초기화 되었다면, 이 변수는 자동으로 전역 컨텍스트에 추가됩니다:

1
2
3
4
5
6
7
8
function showAge() {
// age는 전역 변수입니다.
age = 90;
console.log(age);
}
showAge(); // 90
// age는 전역 변수이므로, 이런식으로도 호출될 수 있습니다.
console.log(age); // 90

아래의 firtName은 둘다 전역 범위입니다. 두번째, firstName은 {} 블럭으로 쌓여있지만, 자바 스크립트는 블럭단위 범위를 지원하지 않는다는 것을 기억하기 바랍니다.

1
2
3
4
5
var firstName = "Richard";
{
var firstName = "Bob";
}
console.log(firstName); // Bob

다른 예제:

1
2
3
4
5
6
7
8
9
10
for (var i=1; i<=10; i++) {
console.log(i); // 1~10까지 출력
}
// 변수 i는 전역 변수입니다. 그러므로, 아래 함수 호출시 i는 for문에서 실행된 후 마지막 값을 가르키게 됩니다.
function aNumber() {
console.log(i);
}
aNumber(); // 11

setTimeout 변수는 전역 범위에서 실행됩니다.

setTimeout 안에서 선언된 모든 함수는 전역 범위에서 실행됩니다. 다음 예제를 주의해서 보십시오.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// setTimeout 함수내에서 사용된 "this"객체는 myObj가 아니라, window객체를 참조합니다.
var highValue = 200;
var constantVal = 2;
var myObj = {
highValue: 20,
constantVal: 5,
calculateIt: function() {
setTimeout(function() {
console.log(this.constantVal * this.highValue);
}, 2000);
}
}
// 전역변수인 highValue와 constantVal을 사용하여 계산됩니다. 200*2.
myObj.calculateIt(); //400

전역 범위를 오염시키지 마십시오

자바스크립트 전문가가 되려면, 가급적 전역 범위에 변수를 생성하는것을 피하도록 해야 합니다.

1
2
3
4
5
// 다음 두 변수는 전역 범위에 있습니다.
var firstName, lastName;
function fullName() {
console.log("Full Name : " + firstName + " " + lastName);
}

다음은, 개선된 코드로서 전역범위를 덜 오염시킵니다.

1
2
3
4
5
// 함수내에 선언함으로서 이것은 지역변수 입니다.
function fullName() {
var firstName = "Michael", lastName = "Jackson";
console.log("Full Name : " + firstName + " " + lastName);
}

위의, 예제에서 fullName() 함수 역시 전역 범위에 있습니다.

변수 호이스팅(Variable Hoisting)

모든 변수선언은 호이스트 됩니다. 호이스트란, 변수의 정의가 그 범위에 따라 선언과 할당으로 분리되는 것을 의미합니다. 즉, 변수가 함수내에서 정의되었을 경우 선언이 함수의 최상위로, 함수 바깥에서 정의되었을 경우는 전역 컨텍스트의 최상위로 변경됩니다.

변수의 선언이 초기화나 할당시에 발생하는것이 아니라, 최상위로 호이스트 된다는 것을 명심해야 합니다. 다음 코드를 주목하십시오.

1
2
3
4
5
6
7
8
9
function showName() {
console.log("First Name : " + name);
var name = "Ford";
console.log("Last Name : " + name);
}
showName();
// First Name : undefined
// Last Name : Ford
// First Name이 undefined인 이유는 지역변수 name이 호이스트 되었기 때문입니다.

이 코드는 자바스크립트 엔진에 의해 다음과 같이 해석됩니다.

1
2
3
4
5
6
function showName() {
var name; // name 변수는 호이스트 되었습니다. 할당은 이후에 발생하기 때문에, 이 시점에 name의 값은 undefined 입니다.
console.log("First name : " + name); // First Name : undefined
name = "Ford"; // name에 값이 할당 되었습니다.
console.log("Last Name : " + name); // Last Name : Ford
}

호이스트 되었을때, 함수 선언은 변수선언을 덮어 씁니다.

1
2
3
4
5
6
7
8
// 다음 두 변수와 함수는 myName으로 이름이 같습니다.
var myName; // string
function myName() {
console.log("Rich");
}
// 함수 선언은 변수명을 덮어 씁니다.
console.log(typeof myName); // function

하지만, 변수에 값이 할당될 경우에는 반대로 변수가 함수선언을 덮어 씁니다.

1
2
3
4
5
6
7
var myName = "Richard";
function myName() {
console.log("Rich");
}
console.log(typeof myName); //string

“strict mode”에서 최초의 선언없이 변수에 값을 할당하려 한다면 오류가 발생합니다. 변수에 값을 할당 하려 할때는 항상 미리 선언하는 습관을 들이는것이 좋습니다.

이 글은 javascriptissexy.com의 허락을 받아 번역 하였습니다. 상업적인 목적이 아니라면 얼마든지 퍼가셔도 좋습니다. 단, 원문과 번역의 차이가 다소 있을 수 있으므로 번역글과 함께 아래 원문 사이트 주소도 항상 첨부해 주시기 바랍니다. 그외 오역이나 개선될 문장이 있다면 트위터 멘션이나 댓글, 이메일등 어떤 경로로든 알려주시면 바로 잡도록 하겠습니다.

Javascript variable scope and hoisting explained