728x90

본 강의는 자바스크립트의 기초를 대충 안다고 가정하고 시작하는 조금 심도 깊은 포스팅이다.

완전 처음부터 배우고 싶다면 다른 블로그나 책의 글을 참조하기를 바란다.

특별한 추가 설명이 없다면 nodejs가 아닌 브라우저에서 사용하는 js를 의미한다.


참고:

javascript docs

ecmascript specification


모든 언어에서는 스코프(Scope)라는 것이 존재한다.

왜냐하면 당연히 변수의 유효범위(스코프)라는 것이 존재해야하기 때문이다.

그로 인해서 지역번수, 전역변수등이 존재하게 되는 것이다.


뭐 사실 대부분의 언어들의 스코프는 비슷비슷 하다.

자바스크립트도 비슷비슷할.... 것 같다면 아마 이런 섹션은 만들어 지지 않았을 것이다.

자바스크립트는 이런 스코프면에서도 변태적인 면을 자랑한다.

그럼 여러 예제들을 보면서 이해해 보도록 하자.


선언하지 않은 변수를 사용할 경우.

<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
console.log(test);
</script>
</body>
</html>

정말 간단한 코드이다.

test라는 변수를 호출하려고 해보자.

어떤 결과가 나올까?


ReferenceError가 나온다.

영어를 읽어보면 변수가 없다로 나온다.

그렇다. 선언하지 않은 변수는 에러가 뜨고 사용할 수 없다.


그러나 자바스크립트를 사용했던 사람은 시큰둥 할 것이다.

아니?? 난 선언 안하고 사용했던 기억이 있는데?

이는 아래와 같은 예시이다.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
console.log(test);
var test = 'hi';
console.log(test);
</script>
</body>
</html>

이러한 코드를 사용해본 기억이 있을것이다.

위의 코드를 사용하면 어떤 결과가 나올까?

test를 선언하기도 전에 test를 호출했으니 위에서 ReferenceError가 나올것 같지만 실제로는 그렇지 않다.


바로 undefiend가 뜨게 된다.

여기서 의문점을 가지게 될 것이다. 도대체 왜 이렇게 되는 것일까?


이는 바로 호이스팅(Hoisting) 때문이다.

호이스팅은 내가 선언한 변수와 함수를 모두 위에 자동 선언하는 것을 의미한다.


즉 위의 사례는 실제로는 아래와 같이 치환된다.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var test;
console.log(test);
test = 'hi';
console.log(test);
</script>
</body>
</html>

이렇게 바뀌기 때문에 ReferenceError가 아니 값을 선언했는데 초기화가 안되서 undefiend가 된 것처럼 나온다.

사실 호이스팅을 이해하기위해서는 js의 스코프를 이해하는게 매우 중요하다.

기본적으로 js의 스코프는 딱 2종류밖에 없다.

지역과 전역이다. 그리고 지역과 전역은 상대적인 면이 있다.

가령 아래의 예시를 보도록 하자.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var global = 'global';
console.log(global);
function f() {
var outer = 'outer';
console.log(outer);
function g() {
var inner = 'inner';
console.log(inner);
}
g();
}
f();
</script>
</body>
</html>

이 예시는 한개의 전역변수와 2개의 지역변수가 있다.

실행한 결과는 뭐 뻔하니 논하지 않겠다.

그럼 3개의 변수를 각각 보도록하자.


var global = 'global';

전역변수이다. 스코프는 전역이다.


var outer = 'outer';

지역변수다. 스코프는 함수 f에서만 유효하다.

따라서 당연히 함수 g에서도 유효하다.


var inner = 'inner';

이 역시 지역변수다. 당연히 함수 g에서만 유효하다.


엄청나게 당연한걸 예시라고 들어놨는데 사실 앞으로의 이야기를 위해서이다.

자바스크립트는 엄청 간단하다.

ECMAScript5에서 스코프를 나누는 기준은 오로지 함수가 유일하다. 오직 함수만이 스코프를 생성할 수 있다.

가령 아래와 같은 코드를 보자.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var global = 'global';
function f() {
var outer = 'outer';
function g() {
var inner = 'inner';
}
g();
}
f();

console.log(global);
console.log(outer);
console.log(inner);
</script>
</body>
</html>

위의 코드는 작동할까?

적어도 outer와 inner의 변수의 유지범위 때문에 값이 제대로 출력되지 않으리라는 것은 쉽사리 알 수 있다.

하지만 다른 문제가 있다. 과연 outer와 inner가 없는 변수 취급을 받아서 RefErr를 내 뱉을까?

아니면 호이스팅 때문에 undefined가 될까??


답은 RefErr를 내뱉는다.

아니 분명 호이스팅 때문에 상단에 선언된다고 하지 않았나??

거기에 대한 대답은 물론 호이스팅이 일어나는것은 맞지만 호이스팅은 스코프를 탄다는 것이다.


즉 inner는 전역 최상단에 호이스팅이 된다.

outer는 f함수에서 호이스팅이 도고 inner는 g함수에서 호이스팅이 된다.

이를 고려하고 프로그래밍을 하지 않으면 매우 심각한 오류를 야기할 수 있다.

여기서 호이스팅의 전역이 의미하는건 당연히 한 스크립트 단위이다.

가령 아래의 코드를 보도록하자.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
console.log(global);
</script>
</head>
<body>
<script>
var global = 'global';

console.log(global);
</script>
</body>
</html>

스크립트가 head에도 있고 body에도 있다.

위의 경우 어떻게 호이스팅이 될까?

이는 head의 script는 당여히 error를 내뱉게 된다.

왜냐하면 호이스팅은 스크립트 단위이기 때문이다.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function myClick() {
console.log(global)
}
</script>
</head>
<body>
<button onclick="myClick()">버튼</button>
<script>
var global = 'global';

console.log(global);
</script>
</body>
</html>

반면 위의 코드는 제대로 동작할까?

호이스팅을 생각해보면 제대로 동작안할거 같지만 사실 그렇지 않다.

스크립트는 나중에 통합되기에 myClick함수는 이미 global이 생긴후에 작동하므로 호이스팅과 상관없이 잘 작동하게 된다.

이렇게 js는 이상한점 때문에 여러가지 에러 코드를 만들게 된다.


호이스팅의 짜증나는점에 더불어서 스코프의 짜증나는 점도 곁들여보도록하자.

스코프와 호이스팅은 서로 단점이 시너지를 일으켜서 사람을 빡치게 만든다.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var global = 'global';
if(true){
var global = 'inner';
}
console.log(global);
</script>
</body>
</html>

위와 같은 코드가 있을 때 여러분의 반응은 궁금하다.

일반적인 통념(C,C++,JAVA)로 접근하자면 global의 출력값은 global이 되야한다.

왜냐하면 if문안에서만 지역변수 global이 유지되야 할테니까.


하지만 여러분은 결과를 보고 뒷목을 잡게 될것이다.

그 이유는 단순하다. 위에도 설명했지만 여러분은 별 생각없이 넘어갔을 텐데

ECMAScript5에서 스코프를 형성하는건 오직 함수뿐이다.

for과 if등의 다른 블록(그 외의 임의 블록들)은 스코프를 생성할 수 없다는 것이다.

이는 좀 불편함을 야기하는데 이를 해결할 방안으로 나온 다른 변수들이 있지만 이는 다음에 설명하도록 하겠다.


여러분들은 이제 최종적으로 가장 암덩어리같은 상황을 보도록하자.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var global = 'global';
function f(){
if(false){
var global = 'inner';
}
console.log(global);
}
f();
</script>
</body>
</html>

자 이상황에서 console.log에 무엇이 출력될까?

아마 일반적인 언어였다면 global이 출력될게 100%다.

사실 자바스크립트내에서 지금까지 배운 지식으로 생각해도 왠지 global이 출려될 느낌이 난다.

그럼 실행해보자.


.... 뭐지?? 라는 생각이 절로 들 것이다.

global도 아니고 inner도 아니고 undefiend??? 도대체 뭐지라는 생각이 들것이다.

이는 호이스팅과 스코프 모두로 생각해봐야한다.


일단 위의 코드는 호이스팅 때문에 아래와 같이 치환된다.

<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var global = 'global';
function f(){
var global;
if(false){
global = 'inner';
}
console.log(global);
}
f();
</script>
</body>
</html>

여기서 뒷목잡을 것이다. 그러나 다시 생각해보면 납득이될것이다.

첫번째로 if문은 스코프가 아니라고 하였다. 그러므로 호이스팅으로 인해서 함수 최상단에 global이 재선언된다.

그렇기 때문에 지역변수가 되었고 지역변수가 전역변수를 가려서 결국 undefiend가 되게 되었다는 슬픈이야기가 있다.


아니?? 그럼 코딩할 때 저걸 다 신경써서 해야합니까?? 라고 묻는다면 "그렇다"이다.

기존은 ECMAScript5에서는 저런 코드를 예방하기 위해서 여러가지 방법을 사용했다.


여러분이 "난 그렇기 하기 싫은디요"라고 생각한다면 다행이도 새롭게 새버전이 나오면서 새로운 해결방법이 등장했다.

그 해결방법에 대해서는 다음에 언급하도록 하겠다.

+ Recent posts