본문 바로가기

JavaScript

자바스크립트의 typeof 연산자에 대해

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

자바스크립트에는 식(expression)의 데이터 형(type)을 알아내기 위한 typeof 연산자를 지원합니다. 가령 다음과 같이 쓸 수 있습니다.


k = 10 > 3;

console.log( typeof k ); //'boolean'

console.log( typeof '지돌스타-쿠키랩' ); //'string'

console.log( typeof a ); //'undefined'


typeof는 다음처럼 함수 호출하듯이 쓸 수도 있습니다.


b = 3;

c =  null;

console.log( typeof( Math.sin ) ); //'function'

console.log( typeof( b ) ); //'number'

console.log( typeof( c ) ); //'object' 


typeof 연산자를 어떻게 쓰던지 실행타임에 자바스크립트 엔진이 알아서 해석하고 구동하는 거겠지요. 함수처럼 호출해야하는 경우는 다음과 같은 경우일 겁니다.


console.log( typeof 10 > 3 ); //'false'


이렇게 쓰면 정말 황당한 결과가 나옵니다. 엔진이 저 코드를 제대로 개발자 의도대로 해석하지 못해 연산자 우선순위가 꼬이는 경우겠지요. 옳게 쓰려면 다음처럼 써야 합니다.


console.log( typeof( 10 > 3 ) ); //'boolean' 


이 연산자를 사용하면 다음과 같은 총 6개의 문자열 객체를 반환합니다. 


'number', 'string', 'boolean', 'object', 'function', 'undefined'


이 반환되는 문자열 객체는 잊지않고 잘 알고 있어야 나중에 typeof를 올바르게 사용할 것입니다. 그럼 저 연산자를 사용해서 몇가지 테스트를 해봅시다. 


console.log(typeof 1); //'number'

console.log(typeof "지돌스타-쿠키랩"); //'string'

console.log(typeof true); //'boolean'


자바스크립트는 값(primitive)과 객체가 분리되어 있습니다.(자바스크립트와 같이 C언어의 복잡성을 감추기 위한 전통적인 C계열의 언어인 Ruby는 값과 객체를 이렇게 분리하지 않았습니다. 자바스크립트는 애초 설계부터 분리가 되었는데 이는 개념의 이원화 문제를 야기시켰습니다.)   즉, 1, "지돌스타-쿠키랩", true등은 값으로 인식되어 typeof에서도 이들을 별도로 구분해줍니다. 다음처럼 해도 위와 같은 결과를 보여줄 것입니다.


num = 1;

str = '지돌스타';

boo = 'boolean';

console.log(typeof num); //'number'

console.log(typeof NaN); //'number'

console.log(typeof str); //'string'

console.log(typeof boo); //'boolean'


이렇기 때문에 변수(자바스크립트에서는 정확하게는 key에 대응하는 value)에 참조되어 있는 이들 3가지 값을 구분하는데는 typeof 연산자를 사용하는 것이 적당합니다. NaN도 'number'임을 기억하세요. 


그럼 값이 아닌 객체의 경우는 어떨까요? 


console.log(typeof new Object()); //'object'

console.log(typeof new Array()); //'object'

console.log(typeof {}); //'object'

console.log(typeof []); //'object'

console.log(typeof /[a-z]/gi); //'object' 

console.log(typeof new Number(1)); //'object'

console.log(typeof new String()); //'object'

console.log(typeof new Boolean(true)); //'object'

console.log(typeof window); //'object'

console.log(typeof new RegExp('[a-z]','gi') ); //'object'


객체의 typeof 연산 결과는 모두 'object'임에 주목하세요. 조금씩 살펴보면 {}, [], /[a-z]/gi는 결국 각각 new Object()와 new Array(), new RegExp()의 리터럴 표기법으로 동일한 인스턴스를 만들어냅니다. Array든 Object든 new연산자를 사용해 생성된 인스턴스 모두 형이 'object'이기 때문에 typeof 연산자로는 이들에 대한 명확한 형을 구분할 수 없다는 것을 알아야 합니다. 또한 new Number, new String, new String 모두 값이 아닌 객체임에도 주목하세요. window도 결국 'object'입니다.


console.log( typeof null ); //'object'

n = null;

console.log( typeof n ); //'object'


null은 'object'이네요. 이것은 자바스크립트 설계때부터 잘못된 것 같습니다. null은 보통 객체 타입의 특수한 값으로 어떠한 객체도 나타내지 않는다는 것을 의미합니다. 그래서 변수에 null이 할당되어 있는지 알아보려면 === 연산자를 사용할 필요가 생깁니다. 불편한 진실이군요.


undefined를 반환하는 경우를 살펴보겠습니다.


console.log( typeof undefined ); //'undefined'

console.log( typeof c ); //'undefined' - c는 한번도 선언된적이 없음

c = 3;

console.log( typeof c ); //'number' - c가 선언되고 값이 할당되었음  

var b;

console.log( typeof b ); //'undefined' - b는 선언되었으나 어떠한 값도 할당된 적이 없음 

b = 4;

console.log( typeof b ); //'number' - b에 값이 할당되었음 

k = null;

console.log( typeof k ); //'object' - k는 애초에 null이 할당되었음 


null과 undefined는 항상 우리를 괴롭힙니다. 뭐가 null이고 뭐가 undefined라는 겁니까? 애매합니다. 여기서 딱 정해드리겠습니다. null은 사용자가 변수에 null을 할당하여 값이 없다는 것을 명시한 것입니다. 하지만 undefined는 사용자가 null을 포함한 어떤 값이나 객체도 할당하지 않은 변수에 접근하거나 정의되지도 않은 변수에 접근하는 경우를 의미합니다. 


그럼 나머지 'function'이 반환되는 경우를 살펴보겠습니다.


console.log(typeof function(){}); //'function'

console.log(typeof Math.sin); //'function'

console.log(typeof isNaN); //'function'

console.log(typeof new Function()); //'function'


함수라고 생각되는 것들에 typeof 연산자를 쓰면 어김없이 'function'을 반환합니다. function(){}은 new Function()으로 대체할 수 있다는 사실을 알고 있다면 new Function()이 'object'가 아닌 것을 쉽게 알 수 있습니다. 사실 함수도 Object이긴 합니다만 특별하기 때문에 typeof로 구분을 질 수 있도록 배려(?)한 것이겠지요. 


그럼, Object, Number, Array, String 자체는 도데체 어떤 존재일까요? typeof를 해보면 나오겠지요?


console.log( typeof Object ); //'function'

console.log( typeof Number ); //'function'

console.log( typeof String ); //'function'

console.log( typeof Array ); //'function'

console.log( typeof Boolean ); //'function'

console.log( typeof RegExp ); //'function'

console.log( typeof Function ); //'function'

console.log( typeof Math ); //'object' -> 대문자라고 해서 다 함수가 아님. 이미 이것은 new Object를 통해 생성된 'object'임 


놀랍지 않나요? 저거 'object'가 아니라 'function'입니다. 함수죠. OOP에 익숙한 사람이라면 'class'정도가 나와야하지 않을까 생각할지 모르겠습니다. 하지만 자바스크립트는 클래스가 없습니다. 그래서 Object, Number, String등은 보통 함수일뿐입니다. 그 이상도 그 이하도 아닙니다. 


재미있게도 Function도 'function'입니다. 그래서 특별하게도 new Function과 Function은 모두 function이 됩니다. 다음은 동일한 결과를 냅니다.


//방법 1

func = new Function( 'a, b', 'return a + b' );

console.log( func( 3, 4 ) );


//방법 2

func = Function( 'a, b', 'return a + b' );

console.log( func( 3, 4 ) );


//방법 3 

func = function( a, b ) {

     return a + b;

};

console.log( func( 3, 4 ) );


조금 다른 이야기로 전개되지만 이왕 나온김에 typeof로 이렇게 구분한 이유를 좀 더 찾는게 좋을 것 같습니다. 


자바스크립트는 모든 것을 동적으로 생성하는데, 이 때 메모리를 사용하는 방법에 있어서 딱 2가지 공간만 만들어낼 수 있습니다.


* Object객체의 key-value 공간

* Function객체의 Scope(유효범위) 공간


그래서 typeof도 이 2개의 공간을 구분하는데 필요한 기능을 제공하는 셈이지요. 


그럼 함수에 new 연산자를 쓰는건 도데체 무엇일까요? 클래스도 아닌데 함수에 new를 써버리는 비상식적인 행동에 OOP개발자들은 혼란에 빠질지 모르겠습니다. 하지만 자바스크립트는 자바스크립트이죠. new를 Java개발자가 쓰는 new로 보면 안됩니다. 함수 앞에 new 연산자를 쓴 것은 'function'을 사용해 'object'를 만들겠다는 의미이고 더 풀어쓰면 드디어 this를 바인딩시킬 수 있는 컨텍스트(context)를 heap 메모리에 생성시킨다는 의미입니다. 그래서 함수가 new 연산자와 함께 쓰일 때 함수를 생성자 함수 또는 생성자라고 부릅니다. 구체적으로 new 연산자 동작방식을 살펴보면 new를 생성자로 쓰일 함수와 함께 선언한 코드가 실행시 해석되면 heap 메모리에 빈 hash맵을 생성하고(a = {}) 그 heap을 컨텍스트에 실어서 생성자 함수를 호출(apply, call)해서 이 생성된 hash맵에 key-value를 할당하는 역할을 합니다. 


참고로 this는 자바스크립트 코드가 실행될 때 한 개의 컨텍스트에 바인딩되며 처음에는 window 컨텍스트에 바인딩됩니다. 이렇게 this에 바인딩된 컨텍스트가 실행 컨텍스트(excution context)입니다. 하지만 'object'에 key-value로 잡힌 함수를 실행하게 되면 이 'object' 컨텍스트가 실행 컨텍스트가 되고 this가 여기에 바인딩됩니다. 물론 함수 실행 종료되면 이전 컨텍스트가 실행 컨텍스트가 되지요. 참고로 바인딩은 묶어준다는 의미인데 이때 바인딩이 되도록 하기 위해 자바스크립트는 프로토타입이라는 것을 만들어 두었지요. 이것은 컴파일 과정이 없는 자바스크립트에게는 어찌보면 필수 조건입니다. 


실행 컨텍스트로 잡는 방법은 간단합니다.


total = 3;

function myFunc(a, b) {

     return this.total + a + b;

}

console.log( myFunc(3, 4) ); //'10'


위 코드를 명시적으로 풀어본다면 결국 아래 코드와 같습니다.(우리는 항상 자바스크립트 코드를 아래 코드처럼 해석할 수 있는 능력을 가져야 합니다. ^^)


window['total'] = 3;

window['myFunc'] =  function (a, b) {

          return this.total + a + b;

};

window['myFunc'].apply( window, [3, 4] ); 


즉 Function 객체에 특수하게 정의된 apply() 및 call() 메서드를 사용해 실행 컨텍스트로 삼을 window를 1번째 인자로 넘겨줍니다. 그럼 이제 myFunc내에서 this를 사용하면 그 this는 window가 됩니다. window는 전역객체이니 일반 Object를 사용해보지요.


total = 3;

object = { 

     total: 10, 

     myFunc: function (a, b) {

          return this.total + a + b;

     }

}; 

console.log( object.myFunc(3, 4) ); //'17'


위 코드도 명시적으로 풀어본다면 다음과 같습니다.


window.total = 3;

window.object = new Object();

window.object.total = 10;

window.object.myFunc =  function (a, b) {

          return this.total + a + b;

};

console.log( window.object.myFunc.apply( window.object, [3, 4] );   //'17'


object.myFunc()이라는 것도 결국 object.myFunc.apply( object … ) 로 바뀐다는 것은 인지하는 것이 중요합니다. 즉 실행컨텍스트로 삼을 object를 인자 넘겨주게 됩니다. 결국 함수가 실행되면서 this를 만나면 이 컨텍스트가 이 this에 바인딩 되어 있게되어 object의 key-value로 선언된 total을 검색해서 값을 반환하게 되겠습니다. 결국 total값은 10이되어 최종적으로 17이 나옵니다. 


하지만 어떻게 정의하든 실행 컨텍스트는 마음대로 바꿀 수 있습니다. 아래의 경우처럼 사용하면 this는 window 컨텍스트가 됩니다. 


object.myFunc.apply( window, [3, 4] ); //'10'


결국 window의 key-value로 정의된 total의 3을 참고하는 경우가 됩니다. 


정리하자면 자바스크립트는 2개의 메모리 공간인 Object객체의 key-value공간, Function객체의 Scope 공간만 존재합니다. 모든 알고리즘이나 상태는 이 2개의 공간에 보존되는 것입니다. 가령 함수내에 var를 썼다면 Function객체의 Scope공간에 상태가 저장되는 것이며 만약 안썼다면 Object객체의 key-value공간에 잡힙니다. 


this는 자바스크립트가 실행시 함수가 호출이 될 때 어떤 컨텍스트를 실행 컨텍스트로 잡을거냐에 따라서 결정됩니다. 종합적으로 미루어 볼때 typeof 연산자로 완벽하진 않지만 크게 값이냐? 객체 공간이냐? 함수 공간이냐? 를 구분하고 싶었던 겁니다. 


참고 

http://zero.diebuster.com/zero/?p=36

http://luvstudy.tistory.com/13



글쓴이 : 지돌스타(http://blog.jidolstar.com/809)