유사배열 객체만들기 (making array-like object in JS)

평소에는 jQuery를 잘 쓰지 않는다. 이미 jindo를 이용해 원하는 형태로 얼마든지 개발할수있기 때문이다. 하나의 라이브러리를 딥하게 쓰다보면, 그 라이브러리에 매우 익숙하게 되고 무엇이 문제인지 무엇이 좋은지 잘 모를때가 있다. 그래서 종종 개인 프로젝트에는 jQuery를 일부러라도 찾아서 쓴다.

몇일전 부터 jQuery를 조금 딥하게 보고 있다가 특이한 점을 발견했다.
바로 jQuery() 객체로 인스턴스를 만들면 다음과 같이 배열로 반환한다는 점이다.

> jQuery()
[]

> jQuery('#test')
[<div id=​"test" class=​"this is a class">​OK​</div>​]

하지만 겉모습만 배열이지 실제로는 jQuery의 인스턴스다.

jQuery() instanceof Array  // false
jQuery() instanceof jQuery // true

오~! 신기한데 어떻게 저럴수 있지? 겉모습은 배열이지만 배열 아닌 유사배열!! 자바스크립트에서 내가 아는 유사배열은 두가지다!. 하나는 arguments 객체고 다른 하나는 셀렉터가 반환하는 NodeList다. 유사배열은 배열이 가지고 있는 배열 특유의 메소드들이 없다. 그래서 어떻게 보면 불편할수도 있다.

그런데 jQuery와 같이 인스턴스가 유사배열 형태로 반환이 되면 장점이 있다. 다음과 같이 DOM을 수정하면 바로 디버거 콘솔창에서 변화를 확인해볼수있다는 사실이다!!

> jQuery('<div>').addClass('test')
[<div class=​"test">​</div>​]

확인해본 결과 웹킷계열의 디버거에서만 위와같이 찍히고, 다른 브라우저들은 그냥 객체로 나온다. 그래도 개발은 크롬에서 주로 하기 때문에 위와같이 콘솔로그만 찍혀도 엄청 도움이 된다.

그래서 만들어봤다. 어떻게 저렇게 하면 나올수있을까?
혹시나 싶어 배열을 프로토타입 상속으로 만들어봤다.

var fine = function(){ };
fine.prototype = Array.prototype;
var x = new fine();

x instanceof fine;  // true
x instanceof Array; // true

그런데 배열을 프로토타입 상속으로 받으면 x는 배열의 인스턴스도 된다. 뭐 누구의 인스턴스가 되냐 안되냐가 그렇게 중요한 문제는 아니지만, 여기서 해당 인스턴스가 되면 필요없는 배열의 메소드들이 fine 객체에 들어갈수가 있다는 것이 문제가 된다.

그럼 어떻게 해야하나? 결론부터 말하면 만들고 싶은 객체에 length와 splice 프로퍼티를 넣으면된다.

> var foo = {length:0, splice:function(){}, x:1, y:1};
> console.log(foo);
[splice: function, x: 1, y: 1]

이런 트릭이 있었다니..대박!! 물론 웹킷계열의 디버거에서만 저렇게 나온다. 요거때메 몇일을 궁리하다가 검색으로 찾아냈는데… 사실은 jQuery 코드에도 위와 같은 코드가 숨겨져 있었다!!

// For internal use only.
// Behaves like an Array's method, not like a jQuery method.
push : core_push,
sort : [].sort,
splice : [].splice

꼼꼼한 레식사마~ ㅋ

자바스트립트 MVC 모델편

글을 쓰다보니 길어져 부득히 시리즈로 나눴다. 항상 이렇게 시리즈로 나누면 중간에 쓰다 마는데.. ㅇㅎㅎ 여튼 뭐 언젠가는 마무리하겠지.. 사실 이글에 앞서 “UML로 코딩하기라는 글”이 있는데 글이 마무리가 안되서, 마무리 되면 공개하는 걸로 하고 그냥 시이작~!

자바스트립트를 보다 구조화해서 코딩 할 수 없을까?

MVC패턴은 요즘 나의 최대 관심사다. 최근 몇년간 TDD니 BDD니 하면서 날코딩과 테스트를 중심으로 작성했던 코드들도 수두룩하다. 하지만 이 코드들은 전부 유지보수에 실패했다! 물론 실패할수밖에 없었던 구구절절한 사연들이 많지만 그건 중요한게 아니므로 패스~ 여튼 결국 난 테스트 주도 프로그래밍에 대해 다시 한번 생각해볼 수 밖에 없었다. 물론 UI 개발에 한정적인 얘기지만 TDD는 뭔가 UI 개발과는 어울리지 않는다는 결론에 이르렀다. 그게 벌써 2년전이다. 그렇게 해서 찾기 시작한 대안이 바로 설계를 바탕으로 하는 프로그래밍이다. 사실 설계라는 것이 스펙에 따라 계속 변경된다고 믿었기에 초기 설계에 그렇게 큰 의미를 부여하진 않았다. 하지만 최근 1년간 MVC 패턴을 이곳저곳에 적용하면서 이른 나의 결론은 초기설계가 중요하고, 잘못된 설계는 나중에 영영 되돌릴수 없다라는 결론도 얻었다. 여기도 사실 현실적인 구구절절한 사연이 있지만.. 패스~ 하지만 확실한 것은 MVC 패턴을 이용한 프로그래밍이 가장 만족스러웠다는 사실이다. 여튼 현실 얘기는 짚어치우고 오늘은 자바스크립트를 이용해 MVC 코드를 어떻게 작성할수있을까? 만을 고민해보자. 사실 나도 MVC를 제대로 이해하고 쓰고 있는건가? 라는 생각을 종종한다. 그만큼 생각의 변화가 많았다. 아마도 공력이 더 쌓이면 또 생각이 바뀌지싶다. 여튼 각설~

자바스크립트로 MVC 구현하기

MVC를 그림으로 그려보면 그 중심에는 당연히 컨트롤러가 있다. 그래서 컨트롤러가 가장 중요한게 아닐까? 라는 생각을 하게 되는데.. 사실 그 말이 맞기도하고 그렇치 않기도하다. 내가 생각할때 MVC는 모델과 뷰 그리고 컨트롤러가 모두 중요하다. 그리고 그 각자의 역할이 매우 명확한데 이것을 최근에야 깨달았다.

MVC 패턴, from 구글 이미지 검색

모델 설계

모델은 매우 중요하다. 특히 모델 구현에 있어서 가장 중요한 것은 Observer 라고 얘기하는 관찰자를 만드는 것이다. 흔히들 MVC 패턴으로 설계된 UML들을 보면 옵저버패턴이 등장하고, 아래 그림과 같이Observer 라는 이름의 클래스도 등장한다. 참고로 난 패턴 용어를 잘 모른다. 어쩌다보니 자연스럽게 알게된 경우가 대부분이다. 혹시 이 글을 읽고 있는 분들도 책으로만 패턴을 공부하지 않기를 바란다. 여튼 훈수는 여기까지… ㅋ

Observer Pattern
일반적인 옵저버 패턴 from wikipidia http://en.wikipedia.org/wiki/Observer_pattern

여기서 Observer 라는 단어의 뜻을 한번 생각해보자. “관찰” 이라는 뜻인데,.. 관찰하다라는 말이 무슨 말일까? 스타크래프트의 옵저버를 연상해도 좋다!. 여튼 옵저버는 현재 상황을 유심히 살펴보다가 누군가에게 알려주는 역할을 한다. 이때 누군가에게 알려주려면 +notify() 메소드를 사용한다.

그럼, Subject는 무슨 뜻일까? 이건 좀 번역하기 애매하다. 여튼 UML에 그려진 대로만 해석하면 Subject 클래스는 옵저버 객체를 관리하는 녀석이다. 관리할 옵저버는 +registerObserver() 메소드를 이용해 등록한다. 그리고 이렇게 등록된 옵저버는 +observerCollection 이라는 변수에 담아 관리한다는 사실도 위 설계도를 보면 알 수 있다.

옵저버 객체 구현

여기까지가 일반적인 옵저버 패턴이고 저 설계대로 구현하려면, 저 설계의 의미를 곱씹어 봐야한다. 일단 Observer와  ConcreteObserverA와의 관계는 상속관계다. 의심할 여지없이 명확하다. 구현도 어렵지 않으므로 패스~!

다음으로 Observer와 Subject와의 관계를 살펴보자. 먼저 Subject 클래스쪽에 빈 마름모가 등장했다. 빈 마름모의 의미는 Subject 객체가 어디선가 생성된 Observer 인스턴스(혹은 객체)를 인자로 받는다는 의미다. 그리고 여기서 또하나 주목해야 할 것이 있다. 바로 의존관계다!. 보통 의존관계는 화살표로 표시하는데, 이 두관계는 눈을 씻고 찾아봐도 화살표가 없다! 그럼 서로 의존성이 없다는 얘긴가? 땡~~!! 틀렸다! 선이 연결되어 있으므로 서로가 서로를 의존한다고 봐야한다.

여튼, Observer와 Subject는 화살표 없이 실선으로 연결되어 있으므로 서로가 서로를 알고 있고, 서로 간의 호출 할수있는 참조를 가지고 있다는 의미가 된다. 그리고 보통 이런 경우는 두 객체를 하나로 합체할 수있다! 뚜둥~!  그래서 그림을 다시 그려보았다.

옵저버 객체
내멋대로 합체한 옵저버 객체

이제 이 설계를 기반으로 구현해보자. 다른 언어로 구현해도 마찬가지겠지만,  클래스가 많아지면 각 클래스간의 역할관계를 정의해야하기 때문에 복잡해진다. 그래서 일단 편한대로 내식대로 옵저버를 설계해봤다.

+ observe( 메시지, 행동 ) : 관찰할 메시지를 등록하고, 메시지를 받으면 행동을 정의한다.

+ notify(메시지, 정보) : 관찰하고 있던 상태가 변경하면 누군가에게 알린다.

옵저버 패턴을 구현할때 핵심은 이벤트를 어떻게 전달하냐인데, 이벤트를 전발하는 방식은 대부분 콜백으로 이루어진다. 즉 콜백함수를 등록해놓고 호출되도록 만든다. 여기서 문제는 자바스크립트가 싱글 스레드라는 사실이다. 싱글 스레드이기 때문에 함수 호출 시점에 따른 의존성도 생길수 있다. 예를 들면 아래와 같다.

notify 메시지가 동기식일 경우 생기는 문제
notify 메시지가 동기식일 경우 생기는 문제

옵저버 객체에서 MethodA() 호출후, notify() 함수를 통해 외부에 MessageC를 날렸다. 그리고 MethodB()를 호출한 상황인데, MessageC 를 받는 객체에서 어떤일이 행해진다면, MethodB()의 호출 시점은 MessageC 를 구현한 객체에 따라 달라진다.

MethodA -> MessageC ( 여러 메소드가 중간에 낑겨들어갈수있음…) -> MethodB

위와 같은 흐름이 보장 된다면, MessageC에서 데이터를 저장하고, MethodB에서 저장한 데이터를 전제로 구현을 해도 큰 문제가 발생하지 않는다.

하지만,  MessageC에서 Ajax 호출이 발생하면 동기식 상황들은 깨지고 만다. 따라서 완간하면 notify() 함수 구현은 반드시 동기식 호출만 하거나 혹은 아예 비동기를 전제로 작성하는게 맘 편할수있다.

나는 그냥 비동기를 가정하고 notify() 호출은 setTimeout을 이용해 메시지 큐에 담아 버렸다. 따라서 위와 같이 호출시 메소드 호출순서는 아래와 같다.

MethodA -> MethodB -> MessageC

여튼 실제 구현한 Observable 객체 다음 링크를 참고한다.

옵저버 객체를 이용한 모델 구현

이제 앞에서 작성한 옵저버 객체를 이용해 모델 객체를 구현해보자. 일단 모델은 Observable 객체를 상속받는다. 자바스크립트는 프로토타입을 이용해 상속받는다. 프로토타입 상속에 관련된 내용은 자바스크립트 가든 번역본을 참고한다.

var SampleModel = function(){};
SampleModel.prototype = new Observable();

다음으로 모델에 저장할 데이터를 정의한다. 데이터를 정의할때는 public으로 정의하고 싶다면 this 객체를 이용해 정의하고, private 으로 정의하고 싶다면 var 문을 이용해 정의한다.

var SampleModel = function(){
    this.publicData = {};
    var privateData = {};
    
    this.getPrivateData = function(){
        return privateData;
    }
};

this.getPublicData = function(){
    return this.publicData;
};

공개 데이터는 아래와 같이 프로토타입 공간에 메소드를 정의해 가져올수도 있다. 하지만 아래 방법보다는 공개 데이터이므로 프로퍼티로 직접 접근해 사용하는 것이 보다 효율적이다.

SampleModel.prototype.getPublicData2 = function(){
    return this.publicData;
}

옵저버 객체에 감지할 이벤트 등록

자, 그럼 옵저버 객체를 이용해 감시할 이벤트를 정의해보자.

var model = new SampleModel();
model.observe({ "CHANGE_STATE" : function(e){
    console.log(e.data) }
});

위 코드는 model에서 발생하는 CHANGE_STATE 메시지를 감지한다.

모델에서 이벤트 발생

자 그럼 CHANGE_STATE 메시지를 직접 발생시켜보자. 메시지는 모델에서 상태가 변경됐을때 발생함을 전제하므로 setData 함수를 만들어 정의한다.

SampleModel.prototype.setData = function(data){
    this.publicData = data;
    this.notify("CHANGE_STATE", {data: this.publicData});
}

여기까지 모델을 살펴봤다. 사실 글쓰다 귀찮아서 마지막은 후다닥 코드만 썼는데.. 나중에 View편을 작성할때 좀더 보강하기로 하고, 오늘은 여기까지… 혹시 질문있으면 댓글 고고씽~

타이타늄 3.0 새버전 릴리즈 노트 뜯어보기

오늘 새벽 타이타늄 3.0이 정식 릴리즈 됐다. 예전에 릴리즈 노트에 그다지 관심이 없었는데… 베타버전을 쓰기 시작하면서 자연히 릴리즈 노트를 챙겨보게 되는것 같다. 500개 이상의 버그가 수정됐다고 한다. 요건 나중에 찬찬히 살펴보기로 하고… 새로 추가된 기능을 훑어보자!

1. On-Device Debugging

오예~ 가장 반가운 소식이다. 이제 시뮬레이터가 아닌 디바이스에서 직접 디버깅이 가능하다!! 관련 문서는 여기를 참고하고, 번역된 문서는 여기를 참고.

2. Alloy Framework

이녀석때문에 베타버전을 이리 깔아서 개발중이었는데… 생각보다 정식판이 빨리 나와주었다!! 이번 릴리즈에 포함된 Alloy의 공식 버전은 0.3.3 이다.

3. Titanium Command-Line Interface

아무래도 Alloy가 node를 이용해 컴파일 하는 과정이 생겨서 그런지 CLI 에도 변화가 있나보다.

4. UI Event Bubbling Changes

드디어 이벤트 버블링을 스탑 (cancelBubble) 시킬수가 있게 됐다. 사실 이전 버전에서는 버블링은 되지만 스탑이 안되서 엄청 불만이 많았는데.. 이젠 된다고 하네~ +_+_+_+ 아마도 모바일 웹버전도 지원해야하기 때문에 웹API와 비슷한 인터페이스는 모두 제공해줄듯 싶다.

5. Android Action Bar Support

안드로이드 액션 바를 지원한다는데… 안드로이드 유저가 아니다보니 정확히 뭘 얘기하는지는 모르겠다. 기회가 되면 알아보는걸로 하고 패스~

6. Accessibility Features

접근성을 높이기 위한 보이스 오버 기능 지원이 추가됐다.