예전에 qunit에서 [code review] 받을 때 [Richard Gibson]이 알려준 내용인데 [jsmocktool] 관련해서 개발하다가 갑자기 생각하서 또 잊을까봐 정리한다.


당시 코드에서 regexp 객체에 flags가 있는지 확인하는 방법으로 속성에 접근해서 아래와 같이 사용했다.


1
return regexp.flags || regexp.toString().match(/[gimuy]*$/)[0];
cs


근데 Richard Gibson이 "나는 불필요한 동작을 하지 않기 위해 in연산자를 사용한다"며 in 연산자를 사용하길 바랬다. 첨에 "엇 뭐지?"라는 생각을 했지만, 잠깐 고민해보니 좋은 의견이였다. 그리고 오늘 글을 쓰기 위해 좀 더 조사를 해봤다.


먼저 속성이 있는지 확인하는 방법은 크게 3가지가 있다.


1. 동등 연산자( == ,=== )

1
2
if(o[k] === undefined){    
}
cs


2. hasOwnProperty 메서드

1
2
if(o.hasOwnProperty(v)){    
}
cs


3. in 연산자

1
2
if(v in o){    
}
cs



이 3가지중 어떤게 가장 빠를까? 한번 고민해보고 아래 글을 읽으면 재밌을거라 생각한다.


시점/브라우저 등 여러가지 상황에 따라 다를 수 있는데 V8(크롬)에서 보면 1번이 가장 빠르다. 왜 그런지는 V8 개발자인 [Vyacheslav Egorov]의 설명을 보면 알 수 있다. 요약하면, 1번의 경우는 흔히 얘기하는 hidden class로 특정 offset을 가지고 있고 반복 호출되면 [Inline Cache] 때문에 굉장히 빠르지만, hasOwnProperty와 in 연산자의 경우는 별도의 캐시를 하지 않고 매번 새로 찾기 때문에 느리다. [비교] (이 말은 키값(offset)이 고정되어 있지 않으면 차이가 없다는 말이다. 실제로 로직에서  키 값을 항상 변경해서 사용하면 성능에 크게 차이가 없다.)


하지만, 비교에서 보면 최근에 hasOwnProperty은 [개선]되어서 지금은 hasOwnProperty도 실제로 비슷한 성능을 내고 있으며 [in 연산자] 역시 언젠가 빠를 것 같다.


다시 돌아와서. 지금까지 보면 1번이 여러 상황에서 적절한 것 같은데 왜 Richard Gibson은 `in`연산자를 사용하라고 했을까?

단순 객체에 속성을 확인하는건 1번이 빠를진 몰라도, 일반적으로 FE을 개발할 때 DOM등 브라우저에 있는 native 객체들에 위의 비교문을 많이 사용한다. 그래서 속성을 읽을 때 단순히 값을 반환하는 경우도 있겠지만, 내부적으로 복잡한 연산이 들어가기 때문에 오히려 1번이 가장 느릴 경우가 많다.


예를 들어. 특정 객체에 offsetHeight라는 속성이 있는지 확인한다면, 1번의 경우는 [forced layout]이 발생하여 엄청나게 느리지만, 2번이나 3번의 경우는 속성이 있는지만 확인하기 때문에 불필요한 forced layout이 발생하지 않는다.


이렇게 단순히 자바스크립트 객체의 속성을 확인하는 방법으로 좋을지 모르지만, DOM이나 기타 native 객체들은 2, 3번(주로 3을 선호한다. 이유는 아래에)을 사용하는게 오히려 좀 더 나은 practice라고 생각하며 아마도 Richard Gibson이 이런 의미로 얘기한 것 같다.


당연하지만, getter의 경우도 1번에서는 동작하지만, 2, 3의 경우는 동작하지 않는다. 참고로 hasOwnProperty는 1, 3번 약간은 다르다. hasOwnProperty은 prototype chaining하지 않고 속성을 찾기 때문에 기대했던 생각과 다른 결과를 만날 수 있다. [참고


요약하면,


단순 오브젝트를 확인하는건 속성에 접근하여 확인하는게 빠르지만, 브라우저의 객체나 DOM은 in/hasOwnProperty가 괜찮은 방법인 것 같다.


ps. Code HighLight을 하기 위해 보통 markup.su/highlighter/ 을 사용했는데 관리를 안하는지 다른 것을 찾아보다가 http://colorscripter.com/ 을 찾았는데 완전 좋네. 



참고 URL.

http://stackoverflow.com/questions/21763617/why-is-getting-a-member-faster-than-calling-hasownproperty

https://bugs.chromium.org/p/v8/issues/detail?id=2472#c8

https://bugs.chromium.org/p/v8/issues/detail?id=2743

Posted by 전용우
,

t3js

프로그래밍 2015. 4. 19. 23:07

토요일인가? 트위터에서 t3js[링크]라는 프레임워크를 만들었다는 트윗을 보고 이건 뭔가하고 주말에 와이프가 드라마를 보는 틈을 타 잠깐 살펴봤다.

t3js을 만든 사람이 니콜라스 자카스[링크]라고 다수의 JS책을 쓰고 JS관련 컨설팅을 하다가 최근에 프로필을 보니 BOX에서 수석 아키텍트를 하고 있다. 이 사람 낸 책 중 번역서는 대부분 봤는데[링크] 다 괜찮았던 책이고 아티클도 꾸준히 쓰는 편이라 신뢰감이 있었다.

먼저, 간단하게 설명을 한면 t3js는 아래 4가지를 알 필요가 있다.

Application - Module과 service을 관리하고 메시지를 전달한다. 모듈간의 커뮤니케이션과 라이프사이클등등 .(하나만 존재)

Module - 흔히 얘기하는 Component같은 역할이다. 돔에 이벤트를 바인딩하는등 application의 비지니스 로직을 담당한다.

Behavior - Module의 중복되는 부분을 뽑아 behavior을 만든 후 moudule에서 사용한다.(사실 module의 공통 부분이라고 생각하면된다)

Service - 유틸리티 같은 코드를 집합이다.(비지니스 로직을 제외한 외부 라이브러리 등등)


이 4가지의 역할을 다이어그램으로 표시하면 아래와 같다.


http://t3js.org/


특징은 보는 것과 같이 module간 커뮤니케이션을 할 수 없고 Application을 통해서만 모뮬간 커뮤니션을 한다. 그리고 jQuery의 의존성을 가지며, 제거할 예정이고 [이슈]에 다양한 라이브러리를 사용할 수 있도록 제안하는 내용이 있어 어떻게 변할지는 모르겠다.

개인적으로 생각하는 이런 류의 프레임워크들에게 기대하는 포인트는 어떻게 메세지를  관리하는가 이다.

규모가 커지면 커질수록 메시지 관리 이슈가 너무 많다.

그래서 이런 문제를 사용자가 덜 겪도록 혹은 해결할 수 있도록 프레임워크에서 적절하게 제어해줘야하는데 t3js는 이런 문제를 실제로 고민했는지 모르지만, 내가 느끼기에 그런 고민을 한 것 같다.

그렇게 생각한 이유가 프레임워크를 만들면 기능을 넣고 싶은 유혹이 많은데... 대표적으로 메세지가 아니라 이벤트처럼 beforeA, afterA와 같은 걸 만들고 싶다. 근데 이걸 만들게 되는 순간 헬..  그리고 다수개의 모듈을 관리하는 객체를 만들어서 관리하고 싶어지는데.. 이것도 만드는 순간 처음에는 좋아보이지만 헬 열림.

여튼 전체적으로 최대한 간단하게 메세지를 처리하려고 한 디자인이 나뻐보이지 않는다. 그렇다고 세련됐다고 보기 힘들어 아쉽다.

그리고 다른 괜찮았던 점은 처음 코드를 볼 때 왜 module context라는 객체가 필요할까 라는 고민이 있었다.아래와 같이 module context객체는 사실상 application의 메서드를 호출하는 수준이다. 

broadcast: function(name, data) {
    this.application.broadcast(name, data);
},
getService: function(serviceName) {
    return this.application.getService(serviceName);
},

그래서 이건 뭘까라는 고민하다가 테스트 작성하는 [문서]를 보고 테스트 때문임을 알았다. 괜찮은 아이디어.


아쉬운 점은 module의 사용법이 굉장히 어색하다.

먼저, 사용 가능한 이벤트가 한정적이고, 아래와 같이 이벤트를 처리하는 방식이 [data-type]속성으로 처리하는데 너무 어색하다.

<footer id="footer" data-module="footer">
    <button id="clear-completed" data-type="clear-btn">Clear completed</button>
</footer>

Application.addModule('footer', function(context) {
    return {
        onclick: function(event, element, elementType) {
            if (elementType === 'clear-btn') {
                // Do something
            }

        }
    };
});

나라면 elementType을 굳이 로직으로 처리해야 하나라는 생각이 들었다. 이런건 사실 delegate을 써서 안에서 처리해야 하는 내용일 것 같은데 아쉬웠다.

코드가 간단해서 간만에 재미있게 봤네.

ps. 근데 왜 t3인거지?


Posted by 전용우
,

this에 대한 글들이 너무 많아서 굳이 설명하지 않아도 되는데 오늘 글을 읽다가 이상한 내용이 있어서 정리도 할 겸해서 글을 쓴다.

전에 함수형 자바스크립트[링크] 봤는데 최근에 보고 싶은 부분이 있어서 다시 보다가 잘못된 부분을 발견했다. 나의 책을 습관 중에 하나가 안다고 생각하는 부분을 건너 읽는데 이번에 자세히 보다가 발견. -_-;

어쨌든, 59 page에 보면 아래와 같은 글이 있다.

var bFunc = function(){return this};
var b = {name : "b", fun : bFunc};

b.fun(); //=> Window 같은 어떤 객체

"의도치 않은 일이 발생했다. 객체 인스턴스 외부에서 함수를 만들었다면 this는 전역 객체를 가리킨다. 따라서 나중에 bFunc를 b.fun필드로 바인딩해도 this객체는 b자신을 가르키지 않는다."


이건 완전 잘못된 설명이다. 처음에 번역이 잘못됐나하고 원서를 봤는데 역시 잘못되어있다. 그래서 재미있게 읽고 있는데 약간의 실망감이 들었다.


일단 많은 사람들이 this가 저자처럼 만들어 질 때 결정된다고 생각하는데 절대 아니다. this는 호출할 때 결정된다. 그래서 같은 함수는 어떻게 호출하냐에 따라 this가 결정되고 this는 실행환경에서는 변경되지 않는다.

위의 예를 들면, bFunc()을 호출하면 저자가 말한 것 처럼 global이다. node.js는 global, 브라우저는 window다.  근데 b.fun();을 호출하면 b객체를 가르킨다. 그럼 b.fun과 bFunc가 다른가? 똑같다. 즉, 호출하는 방법에 따라 this가 결정된다.


그럼 어떻게 결정될까?


복잡하게 설명하면 복잡한데 기본적으로 호출하는 메서드의 "."앞이 this고 없으면 global이다.

즉, b.fun()은 .앞에 b이기 때문에 this가 b이고, bFunc()는 없기 때문에 global(window)가 된다. 이것만 알아도 this을 이해하는데 대부분은 이해된다.


요약하면,

호출할 때 method의 "."앞부분이 this이고 없으면 global이다. this는 실행될 때 결정되고 실행 환경속에서는 변경할 수 없다.

Posted by 전용우
,

Views 와 Widgets사이에 커플링을 막고자 사용함.
뷰의 상하의 간에 메시지를 주고 받고 해야 한다.
일반적인 옵저버 패턴.


Broadcasting and Listening

eventHandlerA.on('A', function(data){ alert(data.msg); });  // alerts 'ALERT!' 

eventHandlerA.emit('A', message);//trigger

emit나 trigger나 같지만, emit는 밖에서 호출할 때, trigger는 안에서 호출할 때.


Piping
이벤트를 아래로 흐르게 해준다.(like 캡처링)

eventHandlerA.pipe(eventHandlerB).pipe(eventHandlerC);
eventHandlerC.on('A', function(data){alert(data.msg)});  // alerts 'ALERT!'
eventHandlerA.emit('A', message);


Subscribing

이벤트를 위로 올려준다.(like 버블링)

eventHandlerC.subscribe(eventHandlerB);
eventHandlerB.subscribe(eventHandlerA);
eventHandlerC.on('A', function(data){alert(data.msg)});  // alerts 'ALERT!'
eventHandlerA.emit('A', message);


Filtering

이벤트를 중간에 필터링 해줌.

var myFilter = new EventFilter(function(type, data) {
    return data && (data.msg === 'ALERT!'); //true or false
});

eventHandlerA.pipe(myFilter).pipe(eventHandlerB);
eventHandlerB.on('A', function(data){
    alert('piped message: ' + data.msg);
});



Mapping 

라우팅하듯이 특정 eventHandler에 이벤트를 전달한다.(개인적으론 불필요하다고 생각)

var myMapper = new EventMapper(function(type, data) { return (data && (data.direction === 'x')) ? eventHandlerB : eventHandlerC; });

eventHandlerA.pipe(myMapper); eventHandlerB.on('A', function(data){ alert('B direction : ' + data.direction); }); eventHandlerC.on('A', function(data){ alert('C direction : ' + data.direction); }); eventHandlerA.trigger('A', {direction : 'x'}); // pipes to eventHandlerB eventHandlerA.trigger('A', {direction : 'y'}); // pipes to eventHandlerC

pipe만 가능하다.(당연한다 subscribe가 되려면 mapping에서 이벤트를 받아야 함)


Arbitration

Mapping하고 비슷한 느낌인데 mode별로 이벤트를 등록할 수 있다.
한번에 하나의 모드만 가능하고 setMode로 하면 해당 모드만 동작.
이것도 왜 있어야 하는지 모르겠다.


var eventArbiter = new EventArbiter();

eventArbiter.forMode('routeA').on('A', function(data){
    alert('subscribed message: ' + data.msg);
});
eventArbiter.forMode('routeB').on('B', function(data){
    alert('subscribed message: ' + data.msg);
});

eventArbiter.setMode('routeA');

eventArbiter.forMode('routeA').emit('A', message); // alerts 'ALERT!'
eventArbiter.forMode('routeB').emit('B', message); // does nothing. Mode is not set.

eventArbiter.setMode('routeB');

eventArbiter.forMode('routeA').emit('A', message); // does nothing. Mode is not set.
eventArbiter.forMode('routeB').emit('B', message); // alerts 'ALERT!'


Event Handling Inside a Widget

자체적으로 몇가지 룰이 있다.

External to a Widget:

widget.trigger : the interface to talk to a widget

widget.on : the interface to listen to a widget

widget.pipe : the interface to pipe from a widget

widget.subscribe : the interface to subscribe from a widget


Internal to a Widget:

receive events via widget.eventInput

broadcast events via widget.eventOutput

widget.emit는 없음.


//아래와 같이 인풋 핸들러를 등록.

EventHandler.setInputHandler(widget, eventHandlerA);
eventHandlerA.on('B', function(data){alert(data.msg)});
widget.trigger('B', message);


Listening

setInputHandler으로 등록한 놈이 받음

// Child widget
function Child(){
    // setup input and output handlers
    this.eventOutput = new EventHandler();
    this.eventInput = new EventHandler();
    EventHandler.setInputHandler(this, this.eventInput);
    EventHandler.setOutputHandler(this, this.eventOutput);

    this.eventInput.on('hires tutor', function(){
        alert('Accepted to Harvard');
    }.bind(this));
}



// Parent widget
function Parent(){
    // setup input and output handlers
    this.eventOutput = new EventHandler();
    this.eventInput = new EventHandler();
    EventHandler.setInputHandler(this, this.eventInput);
    EventHandler.setOutputHandler(this, this.eventOutput);

    this.child = new Child();
    this.eventInput.on('bad report card', function(){
        this.child.trigger('hires tutor');
    }.bind(this));
}

var parent = new Parent();
parent.trigger('bad report card');


Broadcasting

setOutputHandler으로 등록한 얘가 전달.

EventHandler.setInputHandler(widget, eventHandlerA);
eventHandlerA.on('B', function(data){alert(data.msg)});
widget.trigger('B', message);



보면서 느낀건 전체적으로 과한 느낌이 있다.

너무 많다. 이런 경우 나중에 이벤트로만 넣으면 데이터가 흘러갈 때 문제가 있을 것 같다.
특히 mapping은 불필요. 데이터가 deep copy되지 않기 때문에 중간에 값을 바꾸면 디버깅의 거의 불가능하고 경험상 이벤트 종류는 너무 할 정도로 간단하게 가야 한다. 복잡하게 되면 나중에 엄청 고생함.

Posted by 전용우
,

Transition의 현재 상태를 반환하는 객체다.
단독적으로 사용하지 않고 Surface나 Modifier에 붙여서 사용

var state = new Transitionable(0);
state.set(100, {duration : 500});//바로 실행
state.get(); //현재 상태 반환

[x,y,z]좌표값을 넣을 수 있다.
마지막 인자로 함수를 넣으면 콜백이 실행됨.

var state = new Transitionable(0);
state.set(100, {duration : 500}, function(){ alert('done!'); });

즉, Surface나 Modifier에서 set을 호출하고 get을 호출하여 값을 받아 움직인다고 생각하면 될 것 같다.


Transition은 Tween, Physics으로 두개가 있다. 

Tween Transitions

간단하게는 위에처럼 하고 30개의 curve을 제공한다.

var state = new Transitionable(0);
state.set(100, {duration : 500, curve : 'easeInOut'});

혹은 기본적으로 들어 있는 경우가 아닌 경우 직접 아래와 같이 등록해서 사용가능.
물론 만드는 것도 가능.

var TweenTransition = require('famous/transitions/TweenTransition');
TweenTransition.registerCurve('inSine', Easing.inSine);

//ex)
var customCurve = function(t){ return Math.pow(t,2); };
var TweenTransition = require('famous/transitions/TweenTransition');
TweenTransition.registerCurve('custom', customCurve);

Physics Transitions

가속도가 붙은 물리 transition이다.

SpringTransition

- 점을 넘어서 바운스

WallTransition

- 점을 안쪽에서 바운스

SnapTransition

- 스프링과 비슷한데 damping이 항상 들어감.


Tweens vs Physics

정확한 값이 아니고 정확한 타이밍에 이벤트를 발생시켜야 하는게 아니지만, 사용자 이벤트에 따라 변경하고 싶다면 사용한다.



Posted by 전용우
,


famo.us 코드 분석글은 정확한 글이 아니라 공부하면서 정리한 글이라 정확하지 않고 개인적인 의견들이 난무합니다. 정리하는 용도이니 추후 정리가 완료되면 좀 더 정확한 내용을 올릴 예정이니 나중에 정리된 글을 보세요.


앞에선[링크] famo.us에서 Render Tree를 알아봤고 이번에는 Render Tree의 레이아웃을 어떻게 하는지 알아본다.

앞에서 봤듯이 DOM Tree가 아니라 자체 Render Tree을 가지고 있고 레이아웃도 일반적으로 CSS로 하는게 아니라 famo.us에서 제공하는 레이아웃 방법을 사용한다.

레이아웃하는 방법은 Transforms와 Modifiers을 사용하면 된다.


Transforms

Transforms는 이름에서 나오는 느낌처럼 이미지처럼 걍 CSS3을 Transform을 자바스크립트로 옮긴 객체라고 생각하면 된다.

Transforms은 정적인 객체로 쉽게 translate, scale..을 관리할 수 있다. 내부적으로 css의 matrix3d을 사용하기 때문에 4X4 matrix 형식으로 다루는데 16개의 원소가 있는 배열로 관리한다.

Transforms은 아래와 같은 메서드들을 가지고 있고 아래와 같이 반환한다.


다른 메서드들이야 다를건 없는데 inFront, behind는 css의 z-index처럼 노드의 상하를 조절할 때 사용한다. 내부적으론 translateZ을 걍 살짝 추가한다.

Transform는 그냥 사용되지 않고 Scene Graph(보이는 노드들)에 붙여 이동할 때 사용한다.

var surface = new Surface({
        size: [50, 50],
        properties: { background: 'red' }
    });

    var modifier = new Modifier({
        transform : Transform.translate(100, 100, 0)
    });

    context.add(modifier).add(surface);

그리고 아래와 같이 다수개의 Transform들을 합치거나 기존의 Transform에 추가하여 사용하곤 한다.



Modifiers

Transform은 사실 현재 상태를 나타내는 객체로 혼자서 움직이거나 하지 못한다. 위에서 본 예제처럼 Modifiers에 붙여 Scene Graph에 붙여 사용한다. 그리고 Modifiers는 기본적으로  사이즈나 크기등을 조절할 수 있다.

크기의 경우, 아래와 같이 Surface에 size을 undefined로 하고 Modifirer에 지정하면 undefined의 경우 Surface에 사용한다. 만약 Surface의 값이 다 가지고 있다면 Modifiers의 값을 사용하고 있지 않다. 그래서 크기가 유동적이라면 Surface에서 등록하지 않고 Modifiers에서 등록하면 될 것 같다.


  var sizeModifier = new Modifier({size: [200, 200]});

  var surface = new Surface({
    size: [undefined, 100],
    properties: { background : 'red' }
  });

  context.add(sizeModifier).add(surface);


정렬의 경우, 문서에는 Origin,Align의 방법이 있는데 실제 코드에는 origin밖에 없다.

문서가 좀 더딘듯. origin은 몇가지가 지원되며 아래와 같다.



잼있게도 DOM을 직접 움직인다면 margin으로 처리했을 수 있는데 모두 계산하여 translate로 이동한다.


레이아웃을 사용할 때는 Modifiers을 사용한다고 했는데 이게 두가지 방법이 있다.

하나는 StateModifier, 다른 하나는 Modifier이다.

StateModifier는 push-based라고 하고 Modifier는 pull-based라고 한다.

말은 어렵지만, StateModifier는 내부적으로 모든 Transitionable(Translate...)들이 있어 Modifier에 설정하여 넣으면(push) 된다.

var surface = new Surface({
    size: [70, 70],
    properties: { background: 'red' }
});

var stateModifier = new StateModifier({
    opacity: 1
});

context.add(stateModifier).add(surface);

// animate the opacity from 1 to 0 over 500ms using a linear easing curve
stateModifier.setOpacity(
    0,
    {curve: 'linear', duration : '500'},
    function() { console.log('animation finished!') }
);


Modifier는 내부적으로 Transitionable가 없는 빈 상태로 아래와 같이 Transitionable을 연결하여 Modifier에서 Transitionable의 값을 가져와서(pull) 적용할 수 있다.


var surface = new Surface({
    size: [70, 70],
    properties: { background: 'red' }
});

var modifier = new Modifier();

context.add(modifier).add(surface);

// the opacityGetter
var opacityState = new Transitionable(1);

modifier.opacityFrom(opacityState);

// animate the opacity from 1 to 0 over 500ms using a linear easing curve
opacityState.set({
    Transform.translate(100, 0, 0),
    {curve : 'linear', duration : 500},
    function(){ console.log('animation finished!'); }
});

 

이번에는 레이아웃하는 방법등을 알아봤다, 좀더 자세한 내용은 아래 링크를 참고하면 된다.


https://famo.us/guides/layout

Posted by 전용우
,

famo.us 코드 분석글은 정확한 글이 아니라 공부하면서 정리한 글이라 정확하지 않고 개인적인 의견들이 난무합니다. 정리하는 용도이니 추후 정리가 완료되면 좀 더 정확한 내용을 올릴 예정이니 나중에 정리된 글을 보세요.



매주 화요일날 만나서 관심있는 자바스크립트 라이브러리를 분석하는 스터디를 진행 중인데 난 famo.us을 한다. 예전에 facebook에 paper을 웹으로 만들면서 화제가 됬고 최근데 2000만불을 펀딩 받은 것으로 유명하다. 

회사얘기는 대충하고 일단 이 친구들이 말하는게 다양한 폰에서 고퀄의 애니메이션을 보장해준다고 하길래 모바일웹을 개발해 본 입장에서 이게 가능할까라는 생각이 들어서 시작한다.

이번에는 프로젝트의 환경은 어떤지 찾아보고 몇 가지 문서를 보고 구조를 파악했다.


환경

빌드하는 도구가 있을 것 같지만, package.json에는 lint랑 jscs만 한다. 머징한 파일이 있는 것으로 보아 내부적으로 관리하는 것 같다. 컨트리뷰트 과정을 확인해보니 테스트 코드에 대한 내용이 없는데 내부적으로는 어떨지 모르겠다. 그리고 requirejs로 의존성 관리를 한다.


실행

기본적으로 실행하기 위해 몇 가지가 필요하다.

1. famous code : 내가 찾아볼 파일

2. polyfill code : 일부 polyfill들

3. css : 기본 스타일이나 크게 많지 않고 일부다.


구조

이번 내용은 주로 [링크]을 보고 정리한 내용. 

famous는 자체적으로 브라우저와 같은 Render Tree의 형식을 가지고 있다. 그래서 60fps를 가능하게 만들어준다.(왜 그런지는 끝에) famous는 JS로만 개발가능하게 만든 라이브러리로 sencha와 비슷한 컨셉

먼저, Context를 만듬. 브라우저로 따지면 root라고 생각하면 됨.

var context = Engine.createContext();
context.add(surface);

브라우저의 RenderObect와 같이 다수의 node을 가진다.

node의 종류는 Renderables, Modifiers가 있다.

Renderables은 실제 화면에 그려주는 역할을 하는 node로 ContainerSurface, ImageSurface, InputSurface, CanvasSurface, VideoSurface가 있다. 걍 엘리먼트라고 생각하면 된다. 여기서는 surface가 렌더링 되는 최소의 단위고 직접 컨트롤하기 보다는 surface를 통해하기를 말한다.

Modifiers는 움직이는 역할이다. Renderables은 정적인 컨텐츠고 실제로 움직일 때는 Modifiers을 사용한다.

var context = Engine.createContext(); var chain = context.add(modifier); chain.add(surface); var modifier = new Modifier({ transform : Transform.translate(100,200) });


그리고 각 노드들은 트리 형식의 구조를 만들 수 있고 add메서드을 통해 Tree을 만든다.

Chaining Nodes : 
  context                    var context = Engine.createContext();
     │
 modifier1                   context.add(modifier1)
     │                              .add(modifier2)
 modifier2                          .add(surface);
     │
  surface

Branching Nodes : 
      context                var context = Engine.createContext();
   ┌─────┴─────┐
modifier    surface2         context.add(modifier).add(surface1); // left branch
   │
surface1                     context.add(surface2);               // right branch

Views라는 개념이 있는데 이건 그냥 일반적으로 얘기하는 UI컴포넌트의 형식이라고 생각하면 된다. Render Tree에 추가하고 이벤트를 받거나 전달하고 파라메터, 상태를 지정가능. 여기서는 shadow dom과 같은 컨셉이라고 얘기한다. 아마도 직접 돔을 컨트롤하지 않고 View을 컨트롤하기 때문에 그런듯.

정리하면, Surfaces는 컨텐츠, Modifiers는 layout, 구조는 add메서도로 연결.



 Render Tree와 DOM을 비교한 표.

보면 알겠지만, famo.us는 html을 사용하는게 아니라 모두 famo.us객체를 사용한다. 

얻는 장점은 모든지 컨트롤 가능하다. 즉, 돔을 직접 수정한다면 생기는 문제점을 famo.us는 requestAnimationFrame에서 batch로 돌려 개선할 수 있다.

물론 단점은 생각보다 협업하기가 까다롭고, 모든걸 famo.us을 믿고 가야 하기 때문에 쉽지 않은 방법이다.


일단, 여기까지 본 결과 성능이 좋은 이유는 famo.us는 직접 DOM을 컨트롤하지 않고 Render Object을 만들어 컨트롤하기 때문에 requestAnimationFrame으로 batch를 하기 때문이 아닐까 생각하고 있다. 좀 더 살펴보는걸로.


https://famo.us/guides/render-tree


Posted by 전용우
,

V8 빌드하기

프로그래밍 2013. 8. 23. 02:01

V8은 크로미엄에 비해 더 간단하지만 나중을 위해 간단히 적어본다.

V8역시 mac을 기준으로 작성한다.


1. 소스 코드 받기

앞서 크로미엄[링크]에서 삽집하게된 이유가 V8을 먼저 빌드했기 때문이다. -_-

V8은 크로미엄과 다르게 파일을 빠르게 checkout[링크]받을 수 있기 때문에 난 당연히 비슷할 줄 알고 checkout받았는데 생각해보니 너무 무식했다.

git clone git://github.com/v8/v8.git v8 && cd v8

2. 코드 동기화

V8는 git을 이용해서 쉽게 최신 파일을 받을 수 있다.

git pull --rebase origin master


3. 빌드

의존파일을 빌드하기

make dependencies

본인에 환경에 맞게 빌드 설정한다. 난 x64로 실행.

build/gyp_v8 -Dtarget_arch=x64

크로미엄과 같이 xcodebuild로 빌드한다. 그리고 난 debug모드에 공유 라이브러리로 빌드한다.

xcodebuild -project build/all.xcodeproj -configuration Debug -Dcomponent=shared_library


4. 실행

정상적으로 빌드를 하면 "v8/xcodebuild/Debug"에 파일들을 확인할 수 있다. shell을 이용하여 인터렉티브하게 실행해볼 수 있고, d8로 스크립트파일을 실행할 수 있다.


자세한 내용은 링크를 참고하면 된다.


Posted by 전용우
,

가끔씩 아래와 같이 polyfill형식으로 사용하는 경우가 있다.

(function(){
    var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame|| window.msRequestAnimationFrame;
    var caf = window.cancelAnimationFrame || window.webkitCancelAnimationFrame|| window.mozCancelAnimationFrame|| window.msCancelAnimationFrame;

    if(raf&&!caf){
        var keyInfo = {};
        var oldraf = raf;
        raf = function(callback){
        function wrapCallback(){
            if(keyInfo[key]){
            callback();
            }
        }
        var key = oldraf(wrapCallback);
        keyInfo[key] = true;
        return key;
        }
        caf = function(key){
        delete keyInfo[key];
        }
        
    }else if(!(raf&&caf)){
        raf = function(callback) { return setTimeout(callback, 16); };
        caf = clearTimeout;
    }

    window.requestAnimationFrame = raf;
    window.cancelAnimationFrame = caf;
})();

이렇게 바로 실행해버리는 코드는 테스트할때 상황을 만들어야 하는데 상황을 제어하기가 어렵기 때문에 테스트가 어렵다.

예를들어, 난 아래와 같은 테스트케이스를 작성하려고 한다.


  1. requestAnimationFrame만 있는 경우
    1. cancelAnimationFrame을 호출했을 때 cancelAnimationFrame으로 정상적으로 멈추는지 테스트
  2. cancelAnimationFrame만 있는 경우
  3. requestAnimationFrame, cancelAnimationFrame 둘다 있는 경우
  4. requestAnimationFrame, cancelAnimationFrame 둘다 없는 경우
    1. cancelAnimationFrame을 호출했을 때 cancelAnimationFrame으로 정상적으로 멈추는지 테스트


(하위 목록(1-1. 4-1)에 대한 내용을 빼고 상위 목록만 얘기하자. 하위 목록에 대한 테스트를 얘기하면 길어지기 때문에 다음 기회에.)

근데 1,2,3,4의 상황이 브라우저마다 달라서 해당 로직이 정상적으로 실행되는지 테스트하려면 상황에 맞는 브라우저에서 테스트해야한다.


얼마나 귀찮고 복잡한 일인가?


어떻게 할까 고민하다가..

생각난 방법은 브라우저가 아니라 내가 직접 상황을 만들 수 있으면 가능하지 않을까 생각했다.(사실 이게 핵심인 것 같다. testability을 높이려면 개발자가 제어할 수 있는 환경(디자인..)을 만드는게 가장 중요한것 같다.)

그래서 생각한 방법은 바로 실행하는 것이 아니라 실행시점을 제어할것.

처음에는 아래와 같이 함수를 만들어서 처리했다.


function polyfillRequestAnimationFrame(){
    var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame|| window.msRequestAnimationFrame;
    var caf = window.cancelAnimationFrame || window.webkitCancelAnimationFrame|| window.mozCancelAnimationFrame|| window.msCancelAnimationFrame;

    if(raf&&!caf){
        var keyInfo = {};
        var oldraf = raf;
        raf = function(callback){
        function wrapCallback(){
            if(keyInfo[key]){
            callback();
            }
        }
        var key = oldraf(wrapCallback);
        keyInfo[key] = true;
        return key;
        }
        caf = function(key){
        delete keyInfo[key];
        }
        
    }else if(!(raf&&caf)){
        raf = function(callback) { return setTimeout(callback, 16); };
        caf = clearTimeout;
    }

    window.requestAnimationFrame = raf;
    window.cancelAnimationFrame = caf;
}


추가로 window가 전역 객체라서 제어하게 되면 다른 곳에 영향을 줄지 몰라 아래와 같이 인자로 받아서 처리했다.(이 방법은 브라우저뿐만이 아니라 node.js와 같은 환경에서 테스트하기에 좋았다.)

function polyfillRequestAnimationFrame(global){
    var raf = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame|| global.msRequestAnimationFrame;
    var caf = global.cancelAnimationFrame || global.webkitCancelAnimationFrame|| global.mozCancelAnimationFrame|| global.msCancelAnimationFrame;

    if(raf&&!caf){
        var keyInfo = {};
        var oldraf = raf;
        raf = function(callback){
        function wrapCallback(){
            if(keyInfo[key]){
            callback();
            }
        }
        var key = oldraf(wrapCallback);
        keyInfo[key] = true;
        return key;
        }
        caf = function(key){
        delete keyInfo[key];
        }
        
    }else if(!(raf&&caf)){
        raf = function(callback) { return global.setTimeout(callback, 16); };
        caf = global.clearTimeout;
    }

    global.requestAnimationFrame = raf;
    global.cancelAnimationFrame = caf;
}


이렇게 만든후 난 아래와 같이 테스트케이스를 만들었다.


test("requestAnimationFrame만 있는 경우",function(){
    //Given
    var global = {
        "requestAnimationFrame":function(){}
    };

    var oldRequestAnimationFrame = global.requestAnimationFrame;
    
    //Then
    polyfillRequestAnimationFrame(global);

    //When
    notEqual(global.requestAnimationFrame,oldRequestAnimationFrame);
    equal(typeof global.cancelAnimationFrame,"function");

});

이후부터는 내가 직접 global에 적절한 상황을 만들어서 테스트할 수 있기 때문에 쉽게 테스트케이스를 작성할 수 있었다.

생각해보면 정말 단순하고 간단한 문제인데 처음에 고민을 많이 했다. -_-;


ps. 아쉬운건 requestAnimationFrame, cancelAnimationFrame이 둘다 없는 경운데 이땐 전에 쓴글(링크)처럼 clearTimeout이 정상적으로 변경되었다면 정상적이라고 판단했다.

Posted by 전용우
,

최근(?) 자바스크립트에서는 모듈 패턴(Module Pattern)을 많이 사용한다.

이렇게 많이 사용하는 이유는 개인적으로 AMD의 영향이 크지 않을까 생각한다. (AMD의 좀 안좋은 인식이 있는데 이건 나중에 시간이 되면 쓰도록 하고....)

뭐.. 어쨌건 모듈 패턴을 사용하는 이유야 다양하겠지만 대부분은 지금의 자바스크립트에서 지원하지 않는 private 속성을 사용하기 위함이 큰 것 같다.(ECMA6에서는 뭐가 있는것 같은데..)

나는 사실 모듈 패턴을 선호하지 않는 편이다. 그 이유 중 하나가 코드 리딩이 힘들다.

왜냐하면 모듈 패턴은 함수로 감싼 형식이라 구현부를 볼 때 함수의 파라메터가 있다면 제일 마지막 부분을 확인해야 알 수 있고 클로져를 생각보다 많이 사용해 코드 리딩도 좀 힘들다. 그래서 난 개인적으로 모듈 패턴을 사용할 때는 파라메터를 넣지 않기를 선호하며 클로져도 적게 쓰려고 한다.

그리고 모듈 패턴을 쓰면 private의 사용이 많아지고 private이 많아질 수록 테스트는 만들기 힘들어진다. 물론, private를 테스트하기 힘든건 모듈 패턴의 문제는 아니다.

이런 경우 대체적으로 private함수는 직접 테스트하기 보다는 private함수를 사용하는 public함수를 테스트한다.

나도 보통은 그렇게 하는데 한편으론 맘에 안들때도 있다.

예를 들면 아래의 코드형식이다.

(function(){
    var global = this;
    function Stub(vName, sType){
        this.stubMethod = new StubMethod();
    }
    Stub.prototype.with_param = function(){
        return this.stubMethod;
    }

    //private
    function StubMethod(iStub){
    }
    StubMethod.prototype.and_return = function(vReturn){
    }

    global.stub = global.Stub = Stub;
})();

위에 코드를 보면 StubMethod는 외부에서 생성해서 사용하지 않기 때문에 외부에 노출하지 않는다.

그리고 with_param의 반환 값이 StubMethod인지 테스트해야 하는 코드를 아래와 같이 작성하려고 한다.

test("with_param의 반환 값은 StubMethod 인스턴스여야 한다.",function(){
    //Given
    var stubInstance = stub("Stub");
    //When
    var stubMethod = stubInstance.with_param();
    //Then
    ok(stubMethod instanceof StubMethod);
 });

근데 여기서 StubMethod는 외부에 노출하지 않았기 때문에 위와 같은 테스트가 불가능하다.

그래서 테스트하는 방법을 고민해봤는데 몇 가지가 떠올랐다.

첫 번째. 반환 값을 가지고 add_return을 사용하여 테스트한다.(간접적으로)

두 번째.  반환 값에서 add_return메서드가 있는지 확인하는 테스트를 작성해야 한다(덕타이핑과 같이)

세 번째. 키워드를 넘겨서 테스트 중이면 외부에서도 StubMethod를 접근할 수 있게 한다. (이건 좀 구린것 같다.)

처음 고민한건 add_return 함수의 기능을 테스트하면 StubMethod인지 동시에 테스트되는 방법이다. 물론 간접적으로 테스트 되지만 반환 값이 StubMethod인지 테스트하는 부분이 코드로 표현되지 않아서 좀 별루라고 생각했다.

이것 저것 고민하다가 결국엔 덕타이핑으로 판단했지만 여전히 찜찜함은 남는다.

test("with_param은 반환 값이 StubMethod 인스턴스여야 한다.",function(){
    //Given
    var stubInstance = stub("Stub");
    //When
    var stubMethod = stubInstance.with_param();
    //Then
    // ok(stubMethod instanceof StubMethod);
    // StubMethod는 private으로 접근할 수 없어 덕타이핑으로 판단한다.
    equal(typeof stubMethod.and_return,"function");
});


이와 관련한 질문도 많고 답변도 봤는데 나의 고민을 해결해주는 글은 없었다. 그리고 유사한 패턴이 jQuery의 Deferred, Promise인 것 같아서 코드와 테스트 케이스를 보니 위에 경우랑은 좀 차이가 있었다.

결론은 위와 같이 했지만 이런 경우는 어떻게 처리해야 할지 아직 고민이 된다.


ps. 여전히 고민인 건 정말 private로 만들어야만 했는가? 그냥 public이면 되는거 아닌가? 외부에 노출시켜 발생하는 문제에 비해 테스트의 가치가 큰게 아닌가? 등 많은 고민이 되는데 잘 모르겠다.


ps. 정말 오랫만에 블로그를 썼다. 한동안 바쁘다는 이유로 안썼는데 이젠 조금씩 써보려고 하는데 얼마나 갈지는 잘 모르겠다. ㅎㅎ

Posted by 전용우
,