티스토리 뷰

Javascript - 속 깊은 자바스크립트 [3.자바스크립트의 변수]


| 시작하기에 앞서,


자바스크립트는 일단 객체를 기반으로 이루어진 언어이다. 기본형 변수들을 제외하면 모든 객체는 object를 확장하는 형태를 취한다. 따라서, 자바스크립트에서 변수는 함수를 포함한 모든 종류의 객체를 자유롭게 사용할 수 있다. 따라서, 변수를 잘 알고 사용해야 하는데, 자바스크립트에서는 글로벌 변수를 다루는 방식 등 다른 언어와 다른 부분이 있다. 이번 포스팅에서는 이러한 내용을 포함해, 변수를 효율적으로 선언하고 관리하는 방법에 대해 살펴보자.


1. 자바스크립트의 기본형과 typeof


자바스크립트에서 객체가 아닌 기본적인 키워드와 문자로 활용되는 기본형은 다음과 같다.


  • number(숫자)
  • string(문자열)
  • boolean(이진 값)
  • undefined
  • null
  • symbol

특정변수가 어떤형태인지 확인하는 연산자는 typeof 연산자가 있다.


표-typeof 결과 목록

반환값 

 의미

 undefined

정의되지 않은 값 또는 해당값을 가진 변수 

 boolean

 true/false 값 또는 해당 값을 가진 변수

 number

 숫자값 또는 해당 값을 가진 변수

 string

 문자열 값 또는 해당 값을 가진 변수

 object

 객체 또는 갹채를 저장하는 변수

 function

 함수 또는 함수를 저장하는 변수

 symbol

 symbol() 함수로 생성한 키


참고로 type of 연산의 결과는 문자열로 나온다.


여기서, 주의해야 할점은 typeof null 의 결과값이 "object" 인것이다. null이 기본형으로 구분되어 있지만, 결과는 object이다. 이는 최근들어 ECMAScript 에 null 값이 기본형으로 추가되었기 때문이다.


따라서, null을 체크하고 싶으면 typeof 가 아니라, myVariable ===null 과 같이 체크하는 부분을 추가하기 바란다.


| 2. new String("")과 "",그리고 String("")의 차이


문자열을 바탕으로 기본형과 객체의 차이를 살펴보자.


먼저, 기본형의 형태를 알아보는 함수로 typeof가 있었다. 그리고, 객체에 대하여 어떠한 객체인지 확인하는 것은 instanceof 연산자가 있다. instanceof는 typeof와 달리 이항 연산자로 인자를 2개 받으며, 왼쪽 인자가 오른쪽인자의 인스턴스인지를 확인한다.

<script>
console.log(true instanceof Boolean);   // === false
console.log(true instanceof Object);    // === false
console.log([0,1] instanceof Array);            // === true
console.log({name:"unikys"} instanceof Object); // === true;
var color1 = new String("red");
var color2 = "red";
console.log(color1 == color2);              // === true
console.log(color1 instanceof String);      // === true
console.log(color2 instanceof String);      // === false ?!
console.log(color2 instanceof Object);      // === false
console.log(color1 === color2);             // === false
console.log(color1.constructor === String); // === true
console.log(color2.constructor === String); // === true ?!
</script>

위에서 살펴볼점은 instanceof 는 기본형에 대해서는 동작하지 않는다.따라서 true는 기본형이므로 결과값이 false이고, [ ]처럼 배열을 생성하는 표현식은 내부적으로 new Array() 와 같은 동작을 취하게 되므로 true이다.
그리고, String객체와 string 기본형을 비교해보자. new String()으로 생성한 문자열은 instanceof String이 true이지만, 큰따옴표로 생성한 문자열은 값이 false이다. 이의 이유가 바로 String객체로 선언된 color1과 일반 문자열 기본형으로 선언된 color2의 차이인것이다. 추가적으로 color1 === color2 의 결과 역시 false인 것이 증명이 된다.color1은 기본형이 객체인 String객체의 인스턴스이고, color2는 기본형이 문자열이므로 기본형이 다르기 때문인것이다.

<script>
var color1 = new String("red");
var color2 = "red";
var color3 = String("red");
console.log(color1 instanceof String); // === true
console.log(color2 instanceof String); // === false
console.log(color3 instanceof String); // === false
console.log(typeof color1); // === "object"
console.log(typeof color2); // === "string"
console.log(typeof color3); // === "string"
console.log(color3 === color2); // === true
</script>
위를 추가적으로 참고하면 typeof를 통해, object, string으로 나오는 결과의 차이로 더 확연하게 알 수 있을것이다. 추가된 부분이 color3를 String()과 같은 방법으로 스트링을 선언한것인데, 이는 ToSting()이라는 함수를 검색해 참고 바란다.

| 3.글로벌 변수

자바스크립트에서도 글로벌 변수가 존재하지만 다른 프로그래밍 언어들과 마찬가지로 사용하지 않는것이 좋다. 자바스크립트는 웹이라는 특수성 때문에 어떻게 보면, 다른 언어들보다도 더 글로벌 변수의 사용을 조심해야 하고 자제해야 한다.

웹이 다른 애플리케이션들과 다른점이 있다면 다음과 같다.

  • 1.소스와 데이터의 공개성과 다양한 라이브러리등 외부 소스 활용
  • 2.비동기 로직과 이벤트 기반 처리
  • 3.pc와 같이 좋은 성능에서부터 모바일의 안좋은 성능까지 다양한 브라우징 환경
첫번째 특징과 관련해 설명을 보태면, 요즘 브라우저들 기본적으로 자바스크립트 콘솔이나 디버깅 툴을 제공하므로, 이를 이용하면 소스는 기본이고 자바스크립트의 변수도 쉽게 확인이 가능하다. 그리고 글로벌 변수의 사용을 가장 조심해야 하는 이유중 하나는 다른 라이브러리를 사용하거나, 큰 프로젝트 소스를 나누어서 관리 할때 충돌이 일어날수 있어서 이다. 이러한 소스간의 충돌은 모듈화나 클로저를 통해서 대부분 예방할 수 있다고 한다. 그 중 클로저를 사용한 예를 아래에 첨부한다.
<html>
<body>
   <div id="wrapper">
       <button data-cb="1">Add div</button>
       <button data-cb="2">Add img</button>
       <button data-cb="delete">Clear</button>
       Adding below...<br/>
       <div id="appendDiv"></div>
   </div>

   <script>
  (function () {
       var appendDiv = document.getElementById("appendDiv");
           callback = {
               "1": (function () {
                   var div = document.createElement("div");
                   div.innerHTML = "#1";
                   return function () {
                       return div.cloneNode(true);
                  }
              }()),
               "2": (function () {
                   var img = document.createElement("img");
                   img.src="https://t1.daumcdn.net/cfile/tistory/203E5A424F471E3025";
                   return function () {
                       return img.cloneNode(true);
                  }
              }()),
               "delete":function () {
                   appendDiv.innerHTML = "";
                   return document.createTextNode("Cleared");
              }
          };
       function append(e) {
           var target = e.target || e.srcElement || event.srcElement,
               callbackFunction = callback[target.getAttribute("data-cb")];
           appendDiv.appendChild(callbackFunction());
      };
       document.getElementById("wrapper").addEventListener("click", append);
  }());
   </script>
</body>
</html>

이벤트 호출 콜백에 사용하는 함수와 관련된 변수들을 클로저 안에 선언해서 다른소스와의 충돌을 피하고 있는것을 확인 할 수 있다. 특히 div나 img같은 변수는 다른소스와 쉽게 충돌할 수 있으므로 위와 같이 보호하는것이 좋다.
두번째 특징과 관련해 설명을 보태면, 사용자 이벤트로 비동기 로직을 처리하게되면 글로벌 변수때문에이유도 모르고 예상치 못하는 동작을 맞이 하게 될 수도 있는것이다. 예를들어 버튼을 두번 연속으로 누른다던지, 이전응답이 오지 않았는데 입력값을 바꿔 다시 버튼을 클릭하면 사용자는 예상치 못한 결과값을 받게 되는것이다.
세번째 특징과 관련해 설명을 보태면, 모바일 환경에서 컴퓨팅 자원소모는 글로벌 변수 사용문제와 연결된다고 한다. 자바스크립트는 변수의 메모리 관리 전략으로 자바의 가비지 컬렉션을 사용하고 있어서 해당변수를 참조하는 다른 변수나 함수가 있으면 메모리에서 해제하지 않는다고 한다. 자바스크립트에서는 이벤트 콜백함수가 많이 사용되고 있기 때문에 웬만한 글로벌 변수는 하나이상의 함수에서 참조하고 있을 확률이 높다고 한다. 또한 자바스크립트는 동적인 변수 참조가 가능하기 때문에 하위스코프가 살아있다면 상위의 변수들도 참조할수 있는 상태로 남아있게 된다고 한다.
위와 같은 특징들을 파악하고 있는것이 자바스크립트 고급개발자로 되는 지름길일것이다.!
| 4. 글로벌 변수 정의
글로벌 변수는 말그대로 어디서든지 접근 할 수 있는 변수이다. 이러한 글로벌 변수를 정의하는 방법은 몇가지가 있다. 그중 가장 기본적인 것이 script 태그 안에 그냥 var변수를 정의하는 것이다.

<script>
function addOneToTen() {
   sum = 0;
   for (i = 1 ; i < 11 ; i++) {
       sum = sum + i;
  }
   return sum;
}
sum = 0;
for (i = 0 ; i < 10 ; i++) {
   sum = sum + addOneToTen();
}
console.log(sum);
</script>

위의 결과값은 550이라고 생각하기 쉽다. 그러나 55가 나온다 그이유는 addOneToTen 함수의 sum과 i는 글로벌 변수이다. 이는 함수 외부에 있는 루프에도 영향을 끼쳐서, addOneToTen이 처음 호출되고 글로벌 변수 i의 값은 11이 되어서 한번밖에 실행되지 않아 55라는 결과를 호출하게 되는것이다.
추가적으로 위의 예처럼 var을 쓰지않으면 글로벌 변수가 된다고 하는 사람들이 있다고 한다.이는 일부만 맞는 말이고, 정확히 말하면 var 키워드를 쓰지 않으면 addOneToTen의 sum을 예로 들면 현재보다 상위의 스코프를 탐색하면서 sum변수가 있는지 검사하는 단계를 거치게 된다고한다. 이 검사가 글로벌 영역까지 가서 그곳에도 정의되어 있지 않으면 그때 글로벌 영역에 변수를 정의 한다고 한다.
5. window 객체
자바스크립트는 브라우저 환경에서 돌아가도록 만들어졌다. 그래서 글로벌 영역을 다른 언어와는 조금 다르게 구현하고 있다. 글로벌 영역을 하나의 변수로 정의하고(window) 있다는 것이 특이한점이다.

<script>
var myGlobal = "am i in window?";
console.log(myGlobal);
console.log(window.myGlobal);
</script>
위와 같이, 글로벌 변수를 정의하고 나면 window의 속성값으로도 접근할 수 있는것을 확인할 수 있다. 이처럼, window 객체는 글로벌 변수로 선언한 모든 변수를 속성으로 갖는다.
따라서, 위의 콘솔결과는 같은것을 확인할 수 있다. 

그리고, 문자열을 배열의 인덱스처럼 사용해 접근하듯이 window객체는 효율적으로 사용이 가능하다.아래를 통해 살펴보자.
<script>
var myGlobal = "am i in window?";
var myVariableName = "myGlobal";
console.log("1. " + myGlobal);
console.log("2. " + window.myGlobal);
console.log("3. " + window["myGlobal"]);
console.log("4. " + window[myVariableName]);
</script>

위의 소스를 실행하면 모두 같은 글로벌 변수에 접근하여 출력하고 있는것을 확인 할수있다. 추가적인 설명을 덧붙이자면 NaN이나, 자바스크립트 기본 자료형 중 하나인 undefined 모두 window 객체의 속성이라는 점이다.
| 6. 글로벌 변수 선언 방법과 차이
-글로벌 스코프에서 var 키워드를 써서 변수 선언-상위 스코프에서 같은 변수명으로 선언되지 않고, var 없이 바로 변수 사용하는 경우

<script>
var myGlobal = "global";
(function () {
   myGlobal = "global";
}());
</script>
위의 첫번째 var myGlobal 부분이 글로벌 스코프에 var을 써서 변수를 선언한 예이고, 두번째 줄부터가 var없이 바로 변수를 사용하는 예이다.

-window 객체가 재선언되지 않은 스코프에서 window.global 과 같이 속성 추가-window 객체가 재선언되지 않은 스코프에서 window["global"]과 같이 속성 추가

<script>
(function () {
   var window = {
       popup: function () {
           window.open("http://unikys.tistory.com");
      },
       alert: function () {
           alert("I'm not the true alert!");
      },
       open: function (url) {
           alert("I know where you are going... " + url);
      }
  };
   window.alert();
   window.popup();
}());
</script>
글로벌 변수 선언 방법중 세번째와 네번째는 window 객체가 재선언 되지 않은 경우라는 조건을 전제로 하는데, 자바스크립트 특성상 변수를 재선언하는것은 매우 자유로운데 만일 window객체가 위예제처럼 다시 선언되면 , window 객체는 더이상 글로벌 변수를 속성으로 가지는 global object가 되지 않는다.

글로벌 변수 선언 방법의 차이

아래에서, var로 글로벌 변수를 정의 했을때는 1:~~~의 콘솔결과는 undefined로 나오고,2는 정상적으로 출력되며 var없이 글로벌 변수를 정의했을때는 Exists? :false 그리고 그 밑줄은 레퍼런스에러가 나오게 된다. 이는, var로 글로벌 변수를 선언하면 최초 소스 파싱단계에서 미리 글로벌 변수로 정의되어 undefined로 값이 초기화 되어 있기 때문이다.

<script>

//var 키워드로 글로벌 변수정의
console.log("1: " + varExists + " , " + window.hasOwnProperty("varExists"));
var varExists = "Define a global variable with var keyword";
console.log("2: " + varExists);

//var 없이 글로벌 변수 정의
console.log("Exists?: " + window.hasOwnProperty("noVar"));
console.log("1: " + noVar);
noVar = "Define a global variable without var keyword";
console.log("2: " + noVar);
</script>

var 구문을 통해 변수를 정의하면 현재 변수환경에 변수를 생성하고 undefined로 값을 초기화 하는것을 알수있다.
그리고,소스코드의 중간이나 if등으로 분기할때 var 키워드로 변수를 따로 구분하여 정의하는것은 성능에 아무런 영향을 미치지 않는다. 실행환경이라 하면 새로운 스코프가 생성되는 함수로 들어갈 때에 해당하고, if문은 해당사항이 없기 때문이다. 아래 예제를 살펴보면 더 추가적으로 설명을 하겠다.

<script>
function optimizedFunc(flag) {
   if (flag) {
       var lotsOfVariables1, lotsOfVariables2, lotsOfVariables3;
       console.log("1: " + lessVariables);
  } else {
       var lessVariables;
       console.log("2: " + lotsOfVariables1);
  }
}
optimizedFunc(true);
optimizedFunc(false);
</script>

위에서 서로 다른 분기에 있더라도 변수는 이미 정의되어 있어 레퍼런스 에러는 발생하지 않는다. 그러나 함수가 시작되는 부분에 var 키워드와 함께 모든 변수를 아래와 같이 정리하는것이 좋다.

function optimizedFunc(flag) {
   var lotsOfVariables1, lotsOfVariables2, lotsOfVariables3, lessVariables;
   if (flag) {
       console.log("1: " + lessVariables);
  } else {
       console.log("2: " + lotsOfVariables1);
  }
}
optimizedFunc(true);
optimizedFunc(false);
이는 몇가지 장점이 있기 때문이다. 그이유를 아래에서 더 살펴보자.

7. var 키워드의 효율적인 사용
-초기화 실수의 최소화-해당 스코프에서 사용하고 있는 변수에 대한 관리 용이-미니피케이션(minification) 최적화
위는 var 키워드를 모두 모아놓아서 선언하는것의 장점들인데 하나하나 살펴보자.
먼저, 초기화 실수의 최소화는 상단에 모든 변수를 나열해 필요한 변수들에 대하여 초기화 체크를 쉽게 할 수 있다는 점이다. 이는 일단 변수를 정의했다면 기본값인 undefined로 두는것이 아니라 null이나 0과 같은 기본값으로 설정해줘서, 더 확실히 구분할 수 있다.
아래에서 선언을 안했는지여부의 결과값과 초기화를 안했는지여부의 결과값모두undefined로 같게 나오는 것이 그 대표적 예이다.

<script>
console.log("선언을 안한 경우 검사: " + (typeof notExists === "undefined"));
var notInitialized;
console.log("초기화를 안한 경우 검사: " + (typeof notInitialized === "undefined"));
</script>

만약 null이라는 값으로 초기화 했으면 typeof 말고 notinitialized===null 과 같이 검증이 가능해지기 때문이다.
다음으로, 해당스코프에서 사용하고 있는 변수에 대한 관리가 용이 해진다는 점인데, 그 대표적 예가 var를 적당한 위치에 선언해 주지않아 무한루프에 빠지게 된 아래예이다.


<script>
(function () {
   var xhr, i;
   for (i = 0 ; i < 10 ; i++) {
       xhr = new XMLHttpRequest();
       xhr.open("GET", "http://unikys.tistory.com/" + i);
       xhr.onload = function () {
           var json = JSON.parse(xhr.responseText);
           for (i = 0 ; i < 5 ; i++) {
               alert(i + " = " + json[i]);
          }
      };
  }
}());
</script>


마지막이, 미니피케이션의 최적화인데 자바스크립트로 대형 프로젝트를 개발할때 반드시 고려해야 하는 자바스크립트 최적화 기법이다.


<html>
<body>
   <div id="subject0"></div>
   <div id="subject1"></div>
   <div id="subject2"></div>

   <script>
!function(){var e,t,n,s=["1st subject","2nd subject","3rd subject"];for(t=0;t<s.length;t++)e=document.getElementById("subject"+t),e.innerHTML=e.value=s[t],e.addEventListener("click",function(){alert(this.value+" pressed!")});n=new XMLHttpRequest,n.open("GET","http://unikys.tistory.com/list/"),n.onload=function(){var e,t,s=JSON.parse(n.responseText);for(t=0;t<s.length;t++)e=document.getElementById("content"+t),e.innerHTML=s[t]},n.send()}();
   </script>
</body>
</html>

위와 같이 하나의 var 구문으로 변수를 선언함으로써 var 키워드를 두번만 사용하였다.(변수명이 최초선언될때, 최초사용될때) 이를 통해, 소스의 크기를 줄일 수 있다고 한다.
8. 글로벌 변수 최소화하기
글로벌 변수를 사용하는 것이 좋지않다는 것을 확인했으면 최소화하는 방법을 알아야 할것이다. 먼저 대표적인 두가지 방법이아래와 같다.
- 클로저를 이용하는 방법- 모듈/네임스페이스 패턴을 이용하는 방법

이러한 글로벌 변수의 사용을 최소하하기 위한 방법을 선택할 때는 몇가지 기준을 따르면 된다.

  • 잘모르겠다.->클로저방식
  • 자바스크립트 라이브러리를 만들고 싶다. ->클로저방식
  • 다른 개발자와 상호작용하는 소스를 만든다 ->모듈방식
  • DOM에 자바스크립트 코드를 넣고 변수에 접근해야한다. ->모듈방식

위의 기준을 따르면 사실 대부분 클로저를 사용해서 글로벌 변수없이 전체를 로컬변수로 만들어 사용하는것이 좋다고 한다.


| 9. 변수 사용방법과 성능


자바스크립트에서는 변수 사용이 자유롭고 클로저라는 개념때문에 변수 사용이 다소 복잡하다. 클로저끼리 변수가 왔다갔다 하기도 하고, 상위 스코프에 있는 변수를 자주 참조하게 되는 경우도 많다. 특히 콜백 함수를 많이 사용하는 자바스크립트에서는 자주 일어나는 상황이다.


그리고, 상위스코프에 있는 변수를 탐색하는 것은 컴퓨팅 자원을 조금 소모한다. 이런 점들을 최적화하려면, 상위 스코프에 정의된 변수를 하위 스코프의 로컬 변수로, 그 변수를 하위스코프로 끌어내리는것이 좋다고한다. 이는 위의 var키워드 하나에 변수들을 나란히 정의하는것과 함께 적용해 함수내에서 사용하고 있는 변수들에 대한 정보를 쉽게 알 수 있도록 도와주기도 한다.


이렇게 로컬변수에 접근하는것이 약 4~6%정도 성능이 더 좋다고 한다.


| 마치며,


자바스크립트는 다른 언어와는 달리 변수의 선언과 활용이 매우 자유롭다고한다. 이러한 유연한 변수의 활용도와 글로벌 변수나 다른 오류 상황들을 방지하기 위해 위의 내용의 숙지는 필수일것 같다. 자유롭게 변수를 쓰는 만큼 그만큼의 변수 활용능력이나, 글로벌 변수와 같은 스코프 관리가 더 좋은 자바스크립트 개발자가 되는길임을 깨닫게 되었다!



댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
TAG
more
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함