코딩/Javascript

[JS-기초편] 8.변수 유효 범위(variable scope)

드리프트 2020. 12. 16. 22:13
728x170

 

 

변수와 관련된 것을 다시 살펴보겠습니다.

 

우리가 선언하는 변수에는 실제로 언제 사용할 수 있는지 결정하는 어떤 레벨의 가시성이 있습니다.

 

사람이 이해할 수 있는 뜻으로 바꿔 말하면 우리가 변수를 선언한다고 해서 코드의 아무 곳에서나 액세스 할 수 있다는 의미는 아닙니다.

 

우리가 이해해야 할 몇 가지 기본 사항이 있으며, 이는 변수 유효 범위(variable scope)라는 주제에 속합니다.

 

이번 튜토리얼에서는 이미 기존에 본 적이 있는 사례를 살펴봄으로써 변수 유효 범위(variable scope)를 설명 할 것입니다.

 

변수 유효 범위는 매우 어려운 내용이지만, 일단 여기서는 수박 겉핧기 식으로 간단히 살펴볼 예정입니다.

 

 

 

Global Scope (전역)

 

글로벌 스코프(global scope)를 먼저 알아 봅시다.

 

실생활에서 무언가 전 세계적으로 들을 수 있다고 말할 때,

 

실제 그것은 세계 어느 곳에서든지 들을 수 있다는 것을 의미합니다.

 

 

 

 

JavaScript에서도 동일하게 적용됩니다.

 

예를 들어 변수가 전역으로 사용 가능하다고 말하면 페이지의 모든 코드가 이 변수를 읽고 수정할 수 있는 권한이 있음을 의미합니다.

 

무언가를 전역으로 사용하는 방법은 우리의 코드에서 순전히 함수 외부에서 선언하면 됩니다.

 

이를 설명하기 위해 다음 예제를 살펴 보겠습니다.

 

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Variable Scope</title>
</head>

<body>
  <script>
    let counter = 0;
    
    alert(counter);
  </script>
</body>

</html>

 

여기서는 단순히 counter라는 변수를 선언하고 0으로 초기화했습니다.

 

이 변수가 함수 내부에 있지 않고 스크립트 태그 내부에 직접 선언되었기 때문에 counter 변수는 전역 변수로 간주됩니다.

 

이것이 의미하는 바는 counter 변수는 문서에 있는 모든 코드에서 액세스 할 수 있다는 것입니다.

 

아래 코드를 다시 한번 보시죠.

 

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Variable Scope</title>
</head>

<body>
  <script>
    let counter = 0;

    function returnCount() { 
      return counter;
    } 
    
    alert(returnCount());
  </script>
</body>

</html>

 

위 코드에서 counter 변수는 returnCount 함수 외부에 선언되었습니다.

 

그럼에도 불구하고 returnCount 함수는 counter 변수에 대한 전체 액세스 권한을 갖습니다.

 

코드가 실행되면 "alert" 함수는 counter 변수의 값을 반환하는 returnCount 함수를 호출합니다.

 

 

Note: 전역 변수의 다른 얘기

 

자바스크립트에서는 브라우저의 window 객체의 직계 자손일때 그 변수는 전역으로 간주됩니다.

 

함수 외부에서 선언 된 것은 정확히 말하면 브라우저의 window객체의 직계 자손으로 선언된 표현이 더 정확한 말입니다.

 

다음을 보시면 쉽게 확인할 수 있습니다.

 

alert(window.counter === counter);

 

window 객체 아래에 전역 변수가 있다는 것을 알면 우리는 코드의 어느 곳에서나 전역 변수에 액세스 할 수 있습니다.

 

 

Local Scope (지역)

 

변수가 전역으로 선언되지 않는 상황을 보면 약간 흥미로워집니다.

 

즉, 변수의 유효 범위를 진짜로 이해할 수 있다는 얘기입니다.

 

앞서 살펴본 것처럼 전역으로 선언 된 변수는 함수 내에서 액세스 할 수 있습니다.

 

let counter = 0; 

function returnCount() { 
	return counter;
}

 

그러나, 함수 내부에서 선언된 변수는 아무 곳에서나 액세스 될 수 없습니다.

 

즉, 함수 외부에서 액세스 할 때 참조되지 않습니다.

 

function setState() { 
	let state = "on";
} 
setState(); 
 
alert(state) // undefined 

 

위 코드에서 state 변수는 setState 함수 내부에서 선언되었으며 해당 함수 외부에서 액세스하는 것은 허락하지 않습니다.

 

그 이유는 state 변수의 범위가 setState 함수 자체에 로컬(지역적)이기 때문입니다.

 

쉽게 말해 state 변수가 단지 지역 변수라는 말입니다.

 

 

Note: 변수를 선언하지 않고 사용할 때

 

만약 위 코드를 약간 변경하여, 공식적으로 선언하지 않고 state 변수를 초기화하면 변수의 유효 범위가 달라집니다.

 

function setState() { 
    state = "on";
} 
setState(); 
 
alert(state) // "on"

 

위 코드에서 state 변수가 먼저 setState 함수 내부에 있더라도 let, var 또는 const로 선언되지 않았다면 이 변수는 전역변수로 활성화됩니다.

 

어렵죠?

 

 

 

기타 유효 범위

 

만약 모든 것을 변수 유효 범위대로 두면 일이 너무 쉽게 풀릴겁니다.

 

다음 섹션에서는 약간 다른 유효 범위에 대해 살펴 보겠습니다.

 

 

Block Scoping (블록 범위 지정)

 

코드는 블록으로 구성되어 있습니다.

 

블록이란 정확히 무엇일까요?

 

블록은 항상 중괄호로 묶인 JavaScript 코드 모음입니다.

 

예를 들어 다음 코드를 살펴 보겠습니다.

 

let safeToProceed = false;

function isItSafe() {
  if (safeToProceed) {
    alert("You shall pass!");
  } else {
    alert("You shall not pass!");
  }
}

isItSafe();

 

중괄호 쌍을 세면 위 코드에는 세 개의 블록이 있습니다.

 

그 중 하나의 블록은 isItSafe 함수 자체를 묶은 블록입니다.

 

function isItSafe() {
  if (safeToProceed) {
    alert("You shall pass!");
  } else {
    alert("You shall not pass!");
  }
}

 

두번째 블록은 if 문이 있는 블록입니다.

 

...
  if (safeToProceed) {
    alert("You shall pass!");
  } ...

 

세번째 블록은 else 문이 있는 블록입니다.

 

....
  } else {
    alert("You shall not pass!");
  }
...

 

블록 내에서 let 또는 const를 사용하여 선언 된 모든 변수는 해당 블록과 그 안에 포함 된 모든 하위 블록에 대해 로컬입니다.

 

이를 더 잘 이해하려면 다음 코드를 살펴보십시오.

 

function isThePriceRight(cost) {
  let total = cost + 1;

  if (total > 3) {
    alert(total);
  } else {
    alert("Not enough!");
  }
}

isThePriceRight(4);

 

위 코드에서 total 변수는 함수 블록에서 선언되었습니다.

 

if 블록 내에서도 이 변수에 액세스할 수 있습니다. if 블록이 상위 함수 블록의 하위 블록이기 때문에 total 변수는 if 블록에서 액세스 될 수 있습니다.

 

좀더 전문적인 용어로 표현하자면 total 변수는 "alert" 함수의 범위 내(in-scope)로 간주된다고 합니다.

 

다음 상황은 어떨까요?

 

function isThePriceRight(cost) {
  let total = cost + 1;

  if (total > 3) {
    let warning = true;
    alert(total);
  } else {
    alert("Not enough!");
  }

  alert(warning);   // 여기를 주목하세요!
}

isThePriceRight(4);

 

if 블록 안에 warning 이라는 변수가 선언되어 있고 warning 값을 출력하려고 하는 alert 함수가 있습니다.

 

이 경우 블록 외부에 있는 warning 변수에 액세스하려고 하기 때문에 alert 함수는 실제로 true 값을 표시하지 않습니다.

 

alert 함수가 있는 위치에서 볼때 warning 변수는 범위를 벗어난 것으로 간주됩니다.

 

 

 

 

 

Note : var 키워드로 변수 선언하기

과거 자바스크립트 버전에서는 변수는 항상 var 키워드로 선언한다고 되었습니다.

 

let (및 const) 키워드는 변수를 선언할 수 있는 새로 추가된 키워드입니다.

 

과거에 var를 사용했으면 이제는 대신 let을 사용해야합니다.

 

let 이 변수 선언에 더 추천되는 이유에 대해 얘기한 적이 없지만 이제 변수 유효 범위에 대해 공부하면서 let에 대해 더 자세히 살펴 볼 예정입니다.

 

var로 선언 된 변수에는 블록 범위가 없습니다.

 

이전 예제를 수정하여 warning 변수가 let 대신 var 를 사용하여 선언되도록하면 코드는 다음과 같이 변합니다.

 

function isThePriceRight(cost) {
  let total = cost + 1;

  if (total > 3) {
    var warning = true;   // let 대신 var 로 변수 선언
    alert(total);
  } else {
    alert("Not enough!");
  }

  alert(warning);
}

isThePriceRight(4);

 

이전에는 warning 이 let 으로 선언 됐을 때는 alert 함수가 warning 변수가 범위를 벗어 났기 때문에 아무것도 표시하지 않았습니다.

 

var를 사용하면 그렇지 않습니다.

 

true가 출력되는 것을 볼 수 있습니다.

 

그 이유는 let과 var의 주요 차이점 때문입니다.

 

var로 선언 된 변수는 함수 수준에서 범위가 지정되므로 변수가 선언 된 함수 내부 어떤 곳이든지 해당 변수는 범위 내로 간주됩니다.

 

앞에서 살펴본 것처럼 let으로 선언 된 변수는 블록 수준으로 범위가 지정됩니다.

 

var 로 변수를 선언하는 것은 함수 수준의 범위 지정때문에 본의 아니게 변수 선언 관련 실수를 쉽게 할 수 있습니다.

 

이런 이유로, 변수를 선언 할 때 let을 사용하는 것을 선호합니다.

 

 

 

JavaScript가 변수를 처리하는 방법

 

블록 수준의 변수 선언이 이상하다고 생각했다면, 다음 코드를 살펴보십시오.

 

let foo = "Hello!";
alert(foo);

 

예를 들어 위 코드가 실행되면 Hello!의 값이 정확히 나타납니다.

 

만약 변수 선언과 초기화를 코드 끝으로 옮기기로 한다면 어떻게 될까요?

 

alert(foo);
let foo = "Hello!";

 

이 상황에서 위 코드는 오류가 발생합니다.

 

foo 변수가 참조되지 않고 액세스 되고 있기 때문입니다.

 

let을 var로 바꾸면 코드는 어떨까요?

 

alert(foo);
var foo = "Hello!";

 

이 코드가 실행되면 이전에 본 것과는 좀 다릅니다.

 

"undefined"가 표시됩니다. 정확히 무슨 일이 일어나고 있는 걸까요?

 

JavaScript가 scope (전역, 함수 등)를 만나면 가장 먼저 수행하는 작업 중 하나는 전체 코드를 스캔해서 선언 된 변수를 모두 찾는 것입니다.

 

var로 선언된 변수가 발견되면 기본적으로 undefined 로 초기화합니다.

 

let 및 const의 경우 변수를 초기화되지 않은 상태로 둡니다.

 

그리고 그 변수를 scope 의 맨 위로 이동시킵니다.

 

이것이 무엇을 의미하는지 살펴 보겠습니다.

 

처음에 본 코드는 다음과 같았습니다.

 

alert(foo);
let foo = "Hello!";

 

JavaScript가 위 코드를 스캔하면 코드는 다음과 같이 변환됩니다.

 

let foo;
alert(foo);
foo = "Hello!";

 

foo 변수는 코드의 맨 아래에 선언 되었음에도 불구하고 맨 위로 올라갑니다.

 

이 같은 현상을 공식적으로는 호이스팅(hoisting)이라고 부릅니다.

 

let (또는 const)는 호이스팅 될 때 초기화되지 않은 상태로 남아 있다는 것입니다.

 

이 상태에서 초기화 되지 않은 변수에 액세스하려고하면 코드에서 오류가 발생하고 중지됩니다.

 

이전 예제처럼 var를 사용하여 코드를 수정하면 JavaScript에서는 다음과 같은 방식으로 표시됩니다.

 

var foo = undefined;
alert(foo);
foo = "Hello!";

 

변수는 호이스트되었지만 undefined 로 초기화 되었습니다.

 

이렇게 되면 위 코드는 계속 실행됩니다.

 

이 모든 것의 주요 포인트는 다음과 같습니다.

 

실제로 사용하기 전에 변수를 선언하고 초기화하십시오.

 

JavaScript는 관용적인 언어라 우리가 그렇게하지 않는 경우를 위에서 처럼 그냥 처리하는데, 나중에 매우 혼란스럽습니다.

 

 

Closures (클로저)

 

변수 유효 범위에 대한 공부는 클로저를 빼놓고는 마무리 할 수 없습니다.

 

여기서는 말고 다음에 별도로 클로저에 대해 알아 보겠습니다.

 

 

 

결론

이것으로 변수의 유효 범위에 대한 마칩니다.

 

오늘 배운것은 표면적으로는 매우 간단 해 보이지만 보시다시피 완전히 이해하는 데 시간과 연습이 필요한 몇 가지 고유 한 특성이 있습니다.

 

다음편에는 아까 얘기한 클로저에 대해 알아보겠습니다.

그리드형