2015년 2월 17일 화요일

[난해한 Javascript 개념] 변수의 scope

C/C++/Java에 익숙한 나에게 낯선 것들이 Javascript에는 너무나 많다. 지금까지 다루어 본 언어들과는 너무나 다른 functional 프로그래밍 언어인 Javascript의 핵심 요소를 살펴보고 '내 나름대로의 이해'를 시도해 본다.

낯선 것들:
  • first-class function

  • 변수의 scope

  • this 키워드의 의미

  • closure의 개념

  • 비동기적 프로그래밍과 callback






  • 변수의 scope
    C/C++/Java의 변수 scope은 (내가 이해하기엔) 매우 단순하다. {}의 바깥에서 선언하면 {}의 내부까지 영향을 끼치고, 반대로 {}내에 선언하면 바깥에는 영향을 주지 못한다는 것이다. 그리고 {}의 바깥에 변수를 선언하고 {}의 내부에 같은 이름으로 변수를 재선언하면 변수의 scope은 내부/외부로 나뉜다는 것이다. 말이 어렵지 아주 당연한 것이다.
    void foo(){
     int i = 0;
     {
      printf("%d", i); //0 출력
     } 
    }
    void goo(){
      {
       int i = 0;
      }
      printf("%d", i); //error: 'i' undeclared - 변수의 scope이 선언된 {}로 한정
    }
    void hoo(){
      int i = 0;
      {
        int i= 100;
        printf("%d", i); //100 출력 
      }
      i = i+1;
      printf("%d", i); //1 출력  - 동일 변수명 사용시 scope이 분리
    }
    


    Javascript는 C/C++/Java의 규칙과는 조금 다르다. Javascript에서는 function으로 인한 {}가 아닌 경우에는 scope을 구분하지 않는다. 이것은 build를 모두 마치고 실행되는 방식이 아니라 line 순으로 즉시 실행되는 script 언어의 특징으로 인한 것으로 이해가 된다.
    { 
     var i = 100;
     console.log(i); //100 출력
    }
    i = i+1;
    console.log(i);  //101 출력 - {} 내부에 선언된 변수가 {}의 바깥까지 영향을 끼침
    


    함수 외부에 변수를 선언한 경우 '당연하게도' 함수 내까지 영향을 끼친다.
    var foo = function(){ 
     i = 100;
     console.log(i);  
    }
    var i = 0;
    foo();           //100 출력 - function 외부에 선언된 변수가 function 내부에 영향을 끼침
    i = i+1;
    console.log(i);  //101 출력 - function 내부에서 변경된 값이 function 외부에 영향을 끼침
    

    그리고, 함수의 경우엔, 다른 {}와 달리 함수 내에 선언한 변수는 함수내로 scope이 제한된다.
    var foo = function(){ 
     var i = 100;
     console.log(i); //100 출력
    }
    foo();
    i = i+1;   //ReferenceError: i is not defined - 함수 내에 선언된 변수는 그 함수내로 scope이 제약
    console.log(i);  
    


    {} 내부와 외부에 중복된 이름의 변수를 선언한 경우 두 개를 구분하여 scoping하지 않고 마치 같은 변수처럼 사용한다. (값은 갱신된다.)
    var i = 0;
    console.log(i);  // 0 출력
    if(true){ 
     var i = 100;
     console.log(i); //100 출력
    }
    i = i+1;
    console.log(i);  //101 출력  - 동일 변수명 사용시에도 scope이 분리되지 않음 ( C/C++/Java와 다름)
    


    함수의 경우에는 내부에 동일한 이름의 변수를 선언한 경우 C/C++/Java와 동일하게 scope이 구분된다.(이것도 너무나 당연해 보인다)
    var foo = function(){ 
     var i = 100;
     console.log(i); 
    }
    var i = 0;
    foo();           //100 출력
    i = i+1;
    console.log(i);  //1 출력 - 함수의 경우 동일 변수명 사용시 scope이 분리
    


    특이한 점은 var 키워드 없는 변수가 존재할 수 있다는 것이다. 이 경우 변수의 scope은 전역이다. Javascript가 var가 없으면 그 변수는 그것의 parent 또는 grand parent scope에서 그 변수가 선언되어 있다고 '가정'하는 것이다. 이로 인해 그 변수는 전역이 되어 다른 코드에서의 똑같은 이름의 변수와 의도치 않게 혼용되어 있을 수 있다.
    var foo = function(){
     i = 100; //var 없이 정의
    }
    foo(); 
    console.log(i); //100 출력 - var 키워드 없는 변수는 함수 내부에 선언되어도 외부에서 접근 가능 (만약 x()를 실행하지 않은 경우 sss is not defined  Error발생)
    
    Javascript에서는 이를 피하기 위해 선언되지 않은 변수 사용을 막는 모드를 지원하고 있다. "use strict"라는 구문을 사용하여 그 모드로 진입가능하다.
    이 모드는 Javascript 1.8.5 버전부터 추가된 것이라서 지원이 안되는 경우가 있을 수 있다.
    "use strict"
    i = 100; //undefined error 발생
    



    이해한 것을 정리하자면, Javascript의 변수는 함수 안에 선언한 것이 아닌 이상 모두 전역 변수처럼 동작한다. 심지어는 {}안에 선언된 경우도 마찬가지다.
    함수 내에 선언된 변수는 함수 내에만 영향을 끼친다는 점에서 C/C++/Java와 동일하다. 하지만, var없이 선언된 변수는 함수 안에 존재하더라도 함수가 실행이 될 경우 전역으로 동작한다.

    실제 프로그래밍을 할 때는 대부분 함수 내에서 변수를 선언하여 사용할 것이다. 위의 이해를 바탕으로 생각하자면, Javascript에서는 함수 내부에 같은 변수 이름으로 여러 개의 변수을 선언하는 것을 피하는 것이 좋다. 예를 들어 함수 내에서 지역 변수와 for loop 인덱스 변수를 같은 이름으로 선언하여 사용하지 말라. 이로 인해 문제가 발생한다면, 문제가 어디서 발생했는지 찾기가 매우 어려우므로 조심해야 한다.
    위험한 예:
    var foo = function(){
     var i = 100;
     goo(i);
     for(var i = 0; i< array.length; i++) {
      ...
     }
     hoo(i); //어떤 i를 의도한 것인가?
    }
    

    정리

    1. Javascript에서 변수의 scope이 C/C++/Java와 다르므로 실수할 가능성이 높다.

    2. 하나의 함수 내에서는 용도마다의 변수를 각기 다른 이름으로 선언해서 사용하자.

    댓글 없음: