어서와, 개발은 처음이지?

자바스크립트 변수와 스코프(유효범위) 본문

Javascript

자바스크립트 변수와 스코프(유효범위)

오지고지리고알파고포켓몬고 2018. 9. 10. 18:22
반응형


(19.07.11.안내)이론적인 내용이 궁금하시다면 자바스크립트 스코프(scope)도 읽어봐주세요



자바스크립트는 var 문법을 통해 변수를 선언할 수 있습니다. 또한 es2015부터 let과 const라는 문법으로도 선언할 수 있게 되었습니다.

모질라 형님들의 문서에 따르면 var은 함수 스코프(function-level scoped), let과 const는 블록 스코프(block-level scoped)를 갖는다고 합니다.


몇가지 예제를 통해 var, let, const의 특징과 함수 스코프와 블록 스코프의 차이점을 알아보겠습니다.



  블록 스코프  


블록 스코프는 중괄호{ }로 감싸진 범위를 말합니다.

if(true){
  var variable1 = 'I am in a block';
  let variable2 = 'I am in a block';
  const variable3 = 'I am in a block';
  console.log(variable1); console.log(variable2); console.log(variable3);
}
for(var i=0; i<2; i++){
  var variable1 = 'I am in a block';
  let variable2 = 'I am in a block';
  const variable3 = 'I am in a block';
  console.log(variable1); console.log(variable2); console.log(variable3);
}
function func(){
  var variable1 = 'I am in a block';
  let variable2 = 'I am in a block';
  const variable3 = 'I am in a block';
  console.log(variable1); console.log(variable2); console.log(variable3);
}

if의 블록 { }, for의 블록 { }, function의 블록 { }. 이들 모두 블록 스코프라고 할 수 있습니다.



  함수 스코프  


함수 스코프는 블록 스코프 중 함수(function(){ }) 범위를 갖는 스코프를 말합니다.

function func(){
  var variable1 = 'I am in a block';
  let variable2 = 'I am in a block';
  const variable3 = 'I am in a block';
  console.log(variable1); console.log(variable2); console.log(variable3);
}

육안으로 봤을 때엔 특별히 차이가 보이지 않지만 함수 스코프 안에서 var로 선언된 변수는 C나 JAVA의 변수 선언, 스코프에서 볼 수 없던 재밌는 현상을 만들어냅니다.



  var의 특징  


우선 자바스크립트의 전역 스코프(global scope)는 함수 스코프의 내부와 동일하게 동작합니다.

어렵게 들릴 수 있지만 아래 두 코드가 동일하게 동작한다는 뜻 입니다.(당연한 소리를 어렵게 하는군)

var a = 1;
var b = 2;
console.log(a+b);
function main(){
  var a = 1;
  var b = 2;
  console.log(a+b);
}
main();

당연하게 보이실 수 있지만, 위 부분을 언급한 이유는 언젠가 클로저(closure)라는 개념을 만났을 때 var의 함정에 빠지는 것을 예방하기 위함입니다.

var은 함수 스코프 뿐만 아니라 전역 스코프에서 선언되더라도 고유한 특징을 갖습니다.


항상 인식할 수 있도록 두 코드를 함께 작성하겠습니다.


자, 그럼 아래 코드를 보겠습니다.

for(var i=0; i<5; i++){
  var temp = i;
}
console.log(temp);
function main(){
  for(var i=0; i<5; i++){
    var temp = i;
  }
  console.log(temp);
}
main();


<var의 스코프>

미리 말씀드린대로 위 코드는 동일하게 동작합니다.

그렇다면 결과는 어떻게 나올까요?


C나 JAVA등에 익숙하시다면 for문 내에서 선언된 변수 temp가 for문이 종료되며 소멸하고 에러가 발생할거라고 생각하실겁니다.

하지만 공교롭게도 콘솔에 4가 출력되게 됩니다.


다음 코드는 어떨까요?

if(true){
  var temp = 'hi';
}
console.log(temp);
function main(){
  if(true){
    var temp = 'hi';
  }
  console.log(temp);
}
main();

<var의 스코프>

예상하셨겠지만 콘솔에는 hi가 출력됩니다.

이러한 현상은 var이 함수 스코프에서 유효하기 때문에 발생합니다.


함수 스코프안에서 var로 작성된 선언문이 한번 실행된다면 그 함수가 종료되기 전까지 유효하게 됩니다.

이는 블록 스코프로 동작하는 대다수의 언어와 자바스크립트의 큰 차이점 중 하나입니다.


또한 var은 아래와 같은 중복 선언을 허용합니다.

var name = "yuddomack";
var names = ['kim', 'lee', 'park']; // api로 불러왔다고 가정

for(var i=0; i<names.length; i++){
  var name = names[i];
  if(name === 'kim'){ /* do something */ }
}
console.log(name);
function main(){
  var name = "yuddomack";
  var names = ['kim', 'lee', 'park']; // api로 불러왔다고 가정
  
  for(var i=0; i<names.length; i++){
    var name = names[i];
    if(name === 'kim'){ /* do something */ }
  }
  console.log(name);
}
main();

<var의 중복선언>


위 코드는 api로 이름들(names)를 불러오고 'kim'인 사람이 있을 때 무언가 동작한다고 가정한 예제입니다.

외부 스코프에 선언된 name과 for문 내의 name을 주목하세요.

대부분의 언어는 name이 이미 선언되어 있다고 에러를 출력하지만 자바스크립트의 var은 이를 허용합니다.


그리고 무엇보다 중요한 것은 전역 스코프에서 선언된 name(yuddomack)까지 덮어 씌워진다는 점 입니다.

충격적이게도 콘솔에는 'park'이 출력됩니다.

var temp = 'this is temp';
for(var temp=0; temp<5; temp++){
  // do something
}
console.log(temp);

<var의 중복선언>

또한 위의 temp는 5를 출력합니다.

for문에서 선언된 변수도 함수 스코프 내에서 계속 유효하다는 것을 보여주는 단적인 예 입니다.


이런 현상을 이해하려면 호이스팅(Hoisting)에 대해서 아셔야 하며, 고전 자바스크립트에서는 이를 방지하기 위해 클로저(Closure)를 사용하기도 합니다.

var의 이러한 특징은 외부 js라이브러리를 불러와서 사용하는 경우 치명적일 수 있습니다.


호이스팅과 클로저는 다음 글에서 자세히 알아보도록 하겠습니다.




  let의 특징  


var을 사용할 경우 함수 스코프(또는 전역 스코프)에서 변수들이 의도치 않게 동작할 수 있다는 것을 보았습니다.

그동안 이를 방지하기위해 여러가지 방법을 사용해야 했습니다.


하지만 es2015에서 let과 const의 등장으로 스코프에 대한 고민을 덜 수 있게 됐습니다.

(불과 몇년 전에 생겨났습니다. let const를 사용할 수 있는 저는 축복받은 세대라 할 수 있겠습니다.)


let은 보통의 개발자가 알고있는 블록 스코프에서 유효합니다.

if(true){
  var var_name = 'yuddomack';
  let let_name = '윾또막';
}
console.log(var_name);
console.log(let_name);

<let의 스코프>

이전에 봤던대로 var_name은 출력되지만 let_name은 정의되지 않았다는 에러가 발생합니다.

for문 역시 마찬가지 입니다.

for(var var_i=0; var_i<5; var_i++){ }
console.log(var_i);

for(let let_i=0; let_i<5; let_i++){ }
console.log(let_i);

<let의 스코프>

var_i는 5를 출력하지만 let_i는 정의되지 않았다는 에러가 발생합니다.


또한 중복 선언시에도 외부 스코프와 내부 스코프가 다르게 할당된다는 것을 알 수 있습니다.

let name = 'kim';
if(true){
  let name = 'park';
}
console.log(name);

<let의 중복 선언>

콘솔에는 'kim'이 출력됩니다.


이렇게 let으로 선언된 변수는 상식적(?)인 모습을 보입니다.



  const의 특징  


const는 let의 모든 특징을 가지고 있지만 좀 더 엄격합니다.

선언과 동시에 할당이 이루어져야 하고, 재할당이 불가합니다. 

var a;
let b;
const c;

<할당이 이루어지지 않은 const 선언>

const 변수 초기화시 값 할당이 이루어지지 않으면 에러가 발생합니다.

const c = 100;
c = 200;

<const의 재할당>

또한 한번 선언된 const 변수는 재할당할 수 없습니다.

재할당 하려하면 에러가 발생합니다.


C의 define, JAVA의 final과 그 맥락을 같이한다고 할 수 있습니다.


const 변수는 db환경정보, api 응답값 등 변하지 않을 값들을 담을 때 사용합니다.



  정리  


var은 함수 스코프에서 유효합니다.

let, const는 블록 스코프에서 유효합니다.

const는 선언과 동시에 할당이 일어나야하고, 재할당이 불가합니다.





Comments