블로그 내 검색

2012. 9. 7.

CSS PlayGround

아래 글상자에 CSS 셀렉터를 입력해 보세요. 약간의 딜레이 후 선택된 현재 페이지의 요소가 하일라이팅 됩니다.

2012. 7. 30.

IE 이벤트 모델의 메모리 누수

표준 스크립트 엔진과 다르게 마이크로소프트의 JScript 엔진은 클로저(Closure)에 대해 제대로 처리해주지 못하기에 메모리 Leak이 발생하는건 잘 알려져 있습니다.

JScript가 자바스크립트 객체와 DOM 간의 순환 참조 (circular reference) 가 발생할 때, 가비지 컬렉팅을 제대로 수행해주지 못하기 때문입니다.

circular reference
// 순환 참조 코드
// 정상적인 엔진은 이것을 Closure로 이해하며 엘리먼트가 
// 참조를 잃을 경우 카비지 컬렉팅 된다.
// 그러나 JScript 는 순환 참조로 인식하여 
// 서로 참조를 기억하고 컬렉팅되지 않는다.
var actionButton = document.createElement("button");
actionButton.innerHTML = "Click Me";

var pullhandler = function(e) {
    // Element가 Handler에서 참조. Closure.
    actionButton.innerHTML = "Pushed"; 
    pullContent();
};
// Handler를 Element 가 참조.
actionButton.onclick = pullhandler; 

document.body.appendChild(actionButton);

DOM 객체가 이벤트 핸들링 함수같은 자바스크립트 객체를 참조하고, 그 자바스크립트 객체는 DOM 객체를 참조할 때, 클로저가 형성됩니다.

클로저를 잘 이해하는 표준 스크립트 엔진은 이를 잘 처리하여 DOM객체나 자바스크립트 객체로 향하는 다른 참조가 없을 경우 가비지 컬렉팅이 되며 메모리를 해제합니다.

보통 innerHTML 등으로 컨텐트가 없어질 때, removeChild 등으로 엘리먼트가 삭제될 때, 페이지 언로드시 등등에서 발생하지요.

그러나 IE의 JScript의 경우 이러한 가비지 컬렉팅 사이클을 잘못 처리합니다. 사용된 순환 참조가 많을수록 암세포처럼 메모리를 잠식하며 결국엔 브라우저는 죽습니다.

제일 쉬운 해결 방법은 어떤 이유로 객체가 제거될 때, 명시적으로 null 을 주는 것입니다.

try-catch-finally 구문으로 줄 수도 있고, purge 함수를 구현하여 명시적인 해지를 시킬 수도 있습니다.

순환 참조에 대해서는 다음 자료를 참고하면 좋을 것 같습니다.
http://www.slideshare.net/tifftiff/memory-leaks-in-ie (PT, 영문, 추천)

최근 IE8에 와서는 문제가 수정되었다고 하네요.
(IE7 이하는 아직 문제가 많기에 하위호환이 필요한 경우는 고려가 필요합니다.)

2012. 5. 22.

이미지 대치 기법

Image Replacement???

이미지 대치 기법이라고 합니다.
먼저 Text + CSS로 마크업한 뒤 그 내용을 그것을 표현하는 이미지로 가려 실제 일반적인 환경(대표적으로 PC 브라우저)의 유저들에게 Text + CSS의 조합 대신 이미지를 노출하는 기법입니다.

왜 이런 쓰잘데기 없어보이는(?) 일을 할까요.

CSS3의 등장

CSS3 에 되어서는 이제 기존의 플래시나 이미지에 의존하지 않고도 애니메이션이나 재미있는 로고등을 만들어낼 수 있게 되었습니다.

transition 이나 transform 등을 사용해서 여러 효과를 주기도 하고, 예전에는 이미지로 표현했던 border-radiusbox-shadow 등....이제는 CSS로 거의 모든 걸 할 수 있게 되었죠.

하지만 만능은 아니다

하지만 Text + CSS로 모든 걸 할 수 있는 것은 아닙니다. 그리고 해 낸다 하더라도, 복잡한 CSS의 코드에 코드 리딩이 어려워지는 결과를 낳기도 합니다.
(유지보수를 맡게 된 개발자의 한숨이 들릴지도 모르겠네요)

보통 CSS와 텍스트의 조합으로 표현하기 어려운 경우 기존에는 이미지로 디자인을 만들어 페이지에 img 태그나 css의 background 속성으로 표현하는게 일반적이었습니다.
보통 회사의 로고등에 그런 작업을 많이 쓰지요.

그러나 최근에는 이미지가 컨텐트의 내용을 직접적으로 표현하는 것은 비권장되며, 가능한 Text + CSS의 조합으로 컨텐트를 표현하는 것이 추천됩니다.

이유가 무엇일까요.

SEO 와 접근성

그 이유중 제일 큰 것은 SEO 때문입니다. 

SEO는 검색 엔진 최적화(Search Engine Optimized) 의 약자입니다. 일반적인 브라우저에서 사람의 눈으로 보는 HTML문서와는 별개로 검색 엔진이 페이지를 읽었을 때 효율적으로 검색 엔진에게 정보를 전달할 수 있도록 최적화 마크업을 하는 것을 말합니다.

검색 엔진에게 최적화된 문서는

알맞은 태그를 써서
문자열 형식으로
깨진 링크가 없이 전부 도메인의 페이지가 연결

된 페이지를 말합니다.
Semantic Markup 의 방향과 같다고 볼 수 있죠. 둘이 합쳐지면 SEO++ 가 된다 더군요.

또한 사용자가 HTML을 보는 방식은 PC 브라우저뿐이 아닙니다.

맹인을 위한 스크린 리더, 제한적인 모바일 환경의 브라우저...드물지만 텍스트 브라우저 등, 하나의 HTML에 접근하는 방법은 여러개일 수 있습니다.
이럴 경우 이미지로 표현된 컨텐트는 아무래도 접근성이 떨어지며 해석도 힘듭니다.
alt 속성에도 한계가 있습니다.

방법

서론이 길었네요. 다시 본론으로 돌아오죠.
위에 설명한 SEO와 접근성을 높이기 위해 이미지 대치 기법을 사용합니다.

기본 방식은
  • 텍스트 + CSS로 마크업
  • 텍스트를 안보이게 조작
  • 이미지 CSS 속성 등으로 대신 표시
입니다.

코드를 예로 들어보죠

요새 제가 관심있어하는 RequireJS 에 대한 포스팅을 쓴다고 가정합니다

<h1>RequireJS 짱좋네</h1>
<p>어저고저쩌고.....더이상의 자세한 설명은 생략한다.</p>

이렇게 마크업할 경우 일단은 SEO와 접근성에서 합격점을 받을 수 있습니다. 그러나 문서의 제목을 좀더 돋보이고 화려한 효과를 줘서 강조하고 싶습니다. 

SEO와 접근성을 해치지 않으면서 스타일을 적용하여 이미지 대치 기법을 적용해야 하는 상황이 된거죠.

예전에는 원래 표현할 내용을 span 태그 등으로 감싼 뒤 display를 hidden 등으로 감추거나(파너 이미지 대치 기법), h1안의 텍스트를 text-indent 등의 속성으로 글자를 안보이게 한 뒤에 이미지를 h1의 background 속성으로 지정하는 방법(파크 방법)을 많이 썼습니다. 

그러나 이 방법들에는 치명적인 문제가 있는데 첫번째 방법의 경우, 일반적인 스크린 리더등에서는 display: none 등의 비가시성 요소를 해석하지 않아서 문제가 되며, 두번째 방법의 경우에는 브라우저가 CSS를 사용하지만 이미지를 해석할 수 없을 경우 아무것도 볼 수 없습니다.

위 두 방법보다 그나마 제일 안정적이고 어떤 환경에서도 잘 해석되는 방법으로 알려진 방법을 하나 소개합니다. (길더/레빈 방법)

<h1>
    <span></span>RequireJS 짱좋네
</h1>
<p>어저고저쩌고.....더이상의 자세한 설명은 생략한다.</p>

마크업을 위와 같이 의미없는 span 태그를 문자열 앞에 삽입합니다.
(이 부분이 이 방법의 최대 단점입니다. 의미없는 마크업이 추가되어야 합니다.)

이제 스타일을 줍니다. h1을 위치지정으로 바꿉니다

h1 {
    position: relative;
    width: 860px;
    height: 40px;
}

그 다음 span을 절대영역으로 지정하고 크기를 100%로 주고 컨텐트 이미지를 배경으로 지정합니다.

h1 span {
    background: url(requirejs) no-repeat;
    position: absolute;
    width: 100%;
    height: 100%;
}

이렇게 하면 CSS를 꺼도, 이미지를 꺼도 어디서든 텍스트나 이미지 둘중 하나는 볼 수 있습니다.

덧붙여...

사실 위와 같은 방법에 더해, 추가적인 정보를 다른 특이한 브라우저나 리더, 검색엔진에 주고 싶다면 img의 alt 속성을 활용할 수 있습니다.

실제 W3C 사이트의 로고는

<h1 class="logo">
    <a tabindex="2" accesskey="1" href="/">
        <img src="/2008/site/images/logo-w3c-mobile-lg" 
                 width="90" height="53" alt="W3C">
    </a>
    <span class="alt-logo">W3C</span>
</h1>

과 같이 마크업되어 있습니다.
W3C 로고를 텍스트로 코딩해두고, 그 위를 로고 이미지로 덮은 뒤에 그 이미지에도 alt 속성으로 W3C임을 나타내고 있죠.

사람에게 잘보이는 것 외에도 기계에게까지 잘 보여야 하다니...
웹 접근성과 SEO...참 어려운 것 같네요.

2012. 5. 17.

크롬 개발자 도구로 클로저 차근차근 써보기


크롬 개발자 도구와 클로저

전에 포스팅한 자바스크립트 클로저의 글이 좀 부족한 것 같아 이번에는 새로운 접근으로 한번 풀어보려고 합니다.

그리고 이것에 더해서 크롬 개발자 도구의 유용함과 대화형으로 자바스크립트 디버깅을 하는 방법도 같이 알아보죠.
차근차근 글을 따라 타이핑 하면서 하시면 더욱 좋을 것 같습니다(?)

!! 주의 !!
완전 초보분만을 대상으로 한 포스팅입니다.
초보가 아닌 분이 본 뒤 시간낭비가 되도 책임지지 않습니다

준비

일단 크롬 브라우저가 필요합니다.

크롬을 기동하고 새로운 빈 탭을 만듭니다.
크롬의 주소창에 chrome://newtab 을 입력하면 새로운 빈 탭이 열립니다.

그 다음 F12을 눌러 개발자 도구를 엽니다.
개발자 도구가 열리면 상단의 아이콘 중 Console을 클릭합니다.

저기 있네요

이 콘솔에서는 자바스크립트를 대화형 모드로 코딩해볼 수 있습니다. 프로그래머가 코드를 입력하고 Enter를 치면 바로바로 해석해서 결과를 알려주지요.

개발자 도구 콘솔에 코딩하기

이제 준비가 다 됐습니다.
테스트로 아래와 같은 코드를 타이핑합니다.
var hello = "world";
엔터를 누르면 크롬의 자바스크립트 엔진은 위 문장을 바로 해석하여 결과를 돌려줄 겁니다.
위 문자열은 자바스크립트 문장(statement)로서 반환값이 없습니다.
그래서 undefined 가 찍힐 겁니다.

hello 변수에 값이 잘 할당되어 있는지 볼까요?
hello를 타이핑해 봅시다.
hello는 표현식(expression)으로 부르는데 계산되어 값을 표현할 수 있는 단위입니다.

결과는 예상대로...
이제 본격적으로 시작해 봅시다.
다음과 같은 코드를 타이핑해 봅니다. 여러 줄을 걸쳐 입력할 경우 시프트와 엔터를 동시에 누르면 바로 엔진에게 보내지 않고 줄바꿈이 가능합니다.
var createSeq = function() {
    var i = 0;
    return function() {
        return ++i;
    }
};

제대로 입력했다면 undefined 가 출력됩니다. 이것도 물론 문장(statement) 이기에 아무런 값을 반환하지 않죠.
제대로 입력되어 있는지 확인해보기 위해 createSeq 를 입력합니다. 방금 입력한 함수의 내용이 그대로 출력된다면 제대로 입력한 겁니다.
(함수를 실행하지 않고 바로 값을 보려고 할 경우, toSource 라는 함수의 메서드가 자동으로 호출되어 함수의 표현식을 문자열 형식으로 출력하게 됩니다.)

함수 소스를 볼 수 있군요 

이제 저 함수를 실행합니다. 반환값이 있으니 변수 a 에 담아봅시다.
var a = sreateSeq();
아 실수...제가 오타를 쳤군요...

오타;


저렇게 존재하지 않는 함수나 잘못된 문법을 입력할 경우 오류를 출력해 줍니다. 클릭해보면 오류 객체의 내용도 볼 수 있습니다.
다시 제대로 쳐봅시다.
var a = createSeq();

변수 a에는 뭐가 있을지 한번 찍어볼까요.
콘솔에 표시된 반환된 함수의 내용
function () {
    return ++i;
}
입니다.
반환된 함수 내용

그렇다면...a의 실행 결과는 어떨까요...?
일반적으로 생각하면 현재 변수 i가 없으므로 정의되지 않은 변수 오류를 내야 정상일 것 같습니다.
정말 그럴지 한번 실행해 봅시다.
i 는 정의되지 않은 변수일텐데...

어째선지 1이 찍힙니다. 변수 i 를 어디서 참조한 걸까요.
여기가 중요합니다.
전에 분명 createSeq 를 실행하여 함수를 반환받았습니다. 그 createSeq 안에는 변수 i가 선언되어 있지요.
var createSeq = function() {
    var i = 0; // 여기 선언되어 있네요!
    return function() {
        return ++i;
    }
};

우리가 createSeq를 실행해서 얻은 함수 a 는 변수를 찾을 때 자신이 선언된 곳의 변수를 참조하여 찾은 i에 ++ 연산을 수행하여 결과를 1로 반환한 것이죠.

이렇게 하면 어떨까요.
변수 i를 새로 선언합니다. 값을 99 로 줍시다.
var i =99;
전역에 i 를 선언합니다.

a를 실행하면 어떻게 될까요. a의 소스 내용을 다시 보면
function () {
    return ++i;
}

입니다.
그렇다면 결과는 99에 1을 더해 100이 될까요?

음...

결과는 2가 나옵니다.

a가 실행될 때 전역에 선언된 i를 사용하지 않고, 자신이 선언된 createSeq의 변수를 사용했기 때문입니다.

자바스크립트는 코드를 실행할 때 그 코드에 설정된 유효범위를 뒤지는데, 유효범위에도 순서가 있습니다. a의 경우 createSeq의 유효범위가 전역보다 앞에 있다고 보면 정확합니다.

뭔가 신기하다면 a 함수와 같은 기능을 하는 함수를 전역에 선언해보고 실행해 봅시다
var b = function () {
    return ++i;
}
b();

100 이 나오는군요.

변수 i를 전역에 선언했지만 a의 결과는 2

전역의 i에 ++를 수행하여 반환한 겁니다.
b는 createSeq 의 변수에 접근할 수 없는게 확실하죠. (당연한가요...)

“a는 createSeq의 유효범위의 변수를 사용할 수 있다.”

a를 보통 클로저라고 부릅니다. 여기서 좀더 생각해보면 변수 i는 a만 접근할 수 있도록 포장(encapsulation)되어 있다고도 할 수 있습니다. 다른 곳에서는 아무리 해도 a가 접근할 수 있는 i 변수를 읽을수도, 값을 변경할수도 없습니다.

자바스크립트에서는 이러한 클로저의 특성을 사용해서 private, public 등의 개념을 구현하여 각종 패턴에 사용합니다.

백견이 불여일타

크롬 개발자 도구를 사용해서 자바스크립트 코드를 타이핑해보면서 이런저런 시도를 해본다면 좀 더 자바스크립트에 대해 이해가 쉬울거라고 생각합니다.

크롬 개발자 도구 콘솔 창에서는 자동 완성은 물론 배시 쉘등에서 지원하는 방향키로 커맨드 히스토리를 볼러올 수도 있고 어시스트되는 내용으로 객체의 내용도 미리 알 수 있기에 참 유용합니다.

크롬의 DOM API에서는 get으로 시작하는 메서드들이 이런게 있군요

크롬 개발자 도구와 함께 즐거운 웹 코딩 되시길 바랍니다~

2012. 5. 14.

DevFextX Korea 2012 후기

지난 토요일, DevFextX Korea 2012 에 참여하고 왔습니다.
아래는 직접 찍은 사진들.
대형 플래카드가 보이네요

음료수는 무한대로 마실 수 있었습니다.

세션 중간중간에 있던 쉬는시간.

크게 Web, Android, Emerging 3개의 세션으로 나뉘어 진행됐는데요.
전 주로 Web 분야를 들었습니다.

특히 크롬 웹 확장 개발이 제일 인상 깊었네요
바로 크롬 웹 어플리케이션 개발에 뛰어들고 싶을 정도로...

그리고 세션 후 다트언어에 관심있는 사람들끼리 모여서 프론트엔드 기술에 대해 짧게 이야기를 나눴습니다.

NHN에서 진도 프레임워크를 개발하시고 계신 개발자분에게 제가 좀 과감한 발언을 했는데, 그것에 대해 설명을 듣던 도중 시간이 다 되어 이야기는 추후 하기로 해서 조금 아쉬웠습니다.
다트 한국 그룹에 오시는 분이셨으니 앞으로 기회가 많을거라고 생각하며 행사 종료후 집에 돌아왔습니다.

넋두리 몇마디...
전 사실 이런 모임에 나갈 때마다 새로운 지식을 배우기도 하지만 오히려 여기 모이는 개발자와 나와 차이를 비교하며 살짝 좌절도 합니다. 하지만, 뭔가 넘을수 없는 차이를 가진 개발자를 직접 만나서 나 자신도 그것에 조금이나마 따라가고 싶다는 느낌이 계속 개발자 관련 모임에 나가게 되는 거 같네요.

뱁새도 열심히 노력하면 언젠간 황새 풍월정돈 읊을 수 있지 않을까요 ㅎㅎ

다음 모임도 반드시 참여하고 싶을 정도로 유익한 모임이었습니다.



아래는 세션 발표 자료 모음 링크입니다.
https://docs.google.com/folder/d/0B0ucmHKqyC8_WjlzUDFlUVBwZ2M/edit?pli=1

2012. 5. 1.

자바카페 세미나 발표자료

발표 자료 공유합니다.

이런 발표는 처음이라 참 어색하고 긴장됐는데 끝나고 나니 아쉬움 밖에 안남네요.




다음에 다른 기회가 있었으면 좋겠네요.

2012. 4. 18.

자바스크립트를 사용하지 않은 CSS 이미지 갤러리

자바스크립트의 도움 없이, CSS의 의사 클래스(Pseudo-classes)를 사용해서 이미지 갤러리를 구현해보았습니다.
주로 사용한 것은 :target 과 :hover. 아래 #1 ~ #4 링크를 클릭해보세요~
Firefox나 Chrome 에서 잘 보입니다.

주의 : IE8 이하는 동작하지 않고, IE9는 트랜지션 효과가 제거되어 동작합니다.

사용한 CSS.
div#slide_container {
    position: relative;
    margin: 0 auto;
    width: 480px;
    height: 320px;
}

ul {
    list-style: none;
    margin: 0px;
    margin-top: 20px;
    padding: 0px;
}

ul li {    
    height: 0px;
    overflow: hidden;
    opacity: 0;    
}

ul li img {
    margin: 0 auto;
    height: 320px;    
    box-shadow: 2px 3px 3px #666;
}

ul li:target {
    -webkit-transition: all 800ms ease-in-out;
    -moz-transition: all 800ms ease-in-out;
    -o-transition: all 800ms ease-in-out;
    transition: all 800ms ease-in-out;    
    opacity: 1;
    height: 320px;
}

  • #1
  • #2
  • #3
  • #4
  • 링크를 클릭하세요!

2012. 4. 16.

Spring in Action 3th Edition

토비의 스프링 3.0을 2번 읽은 뒤에 뭔가 부족하다 싶어 회사 주문으로 구매한 책.

토비의 스프링을 두번 읽은 효과가 있는지 책은 금방 읽혔습니다.
책에서 설명하는 내용의 깊이는 고급기술보다는 스프링의 기본 기능에 대해서 죽 설명해나가는 방식이며, 저자가 가끔 던지는 농담조차도 번역이 잘 되어 있어서 번역 수준은 아주 훌륭합니다. (부족한 부분은 역주가 달려 있습니다)

스프링의 기본인 Ioc/DI 부터 프록시, 애스펙트, @MVC, 트랜잭션 추상화 외에도 JMS, JMX, 시큐리티, RMI 등등의 다소 넓은 범위까지 설명하고 있습니다.

만일 어느정도의 자바 기본 레벨이 갖추어져 있고, 토비의 스프링같이 두꺼운 책이 부담된다면 이 책을 추천할만 합니다. 경쾌(?)한 설명으로 스프링의 기본을 학습할 수 있습니다.

그런데 이 책의 샘플 코드 구현이 spitter라는 제목의 소셜네트워크 프로그램인데...
저자의 다소 더러운(?) 센스...

2012. 4. 13.

REST

데이터는 왕
프로그래밍은 결국 데이터를 어떻게 생성하고 변형하고 지지고 볶고 나타내고 보여줄까 하는 목적의 집합체입니다.

프로그래밍의 분야에 따라, 만들려는 아웃풋의 종류에 따라 데이터의 비중이 적거나 많거나 할 수 있겠지만, 데이터가 없는 프로그램은 있을 수 있을까요.
최근에는 빅데이터라는 신조어(?)가 나돌면서 데이터 처리에 대한 관심까지 높아지고 있죠.

이렇게 소중한 데이터를 서로 나누는 기술은 오래전부터 발전해왔습니다.
맨발로 뛰는 스니커(sneaker) 통신부터 맨땅 01010101의 로우레벨 소켓, 그리고 프토토콜을 덧씌운 TCP/IP에서 좀더 상위레벨인 RPC, SOAP, REST 등등 많지요.

여기서는 REST에 대해 알아볼까 합니다.


Representational Status Transfer.

  • 표현 (Representation)
    • 데이터, 즉 리소스의 표현에 구애받지 않음.
    • 표현 형식(format)은 어떠한 것이든 전부 허용함.
  • 상태 (Status)
    • 데이터의 표현 상태에 관심이 많음. 
    • 데이터를 어떻게 해보려는 액션보다 리소스 자체의 표현되는 상태에 대해 비중을 더 둠
  • 전달 (Transfer)
    • 서로 다른 장치간에, 전달하고 전달받는 데이터.

>> 표현 + 상태 + 전달
정리하면, 어떠한 형식이든 적합한 형태의 표현 형식으로 서버 - 클라이언트간의 리소스를 전달하는 것을 말합니다.


Resource And Verb
REST는 리소스를 나타내는 형식인  URI 와, 그 리소스에 대한 액션을 나타내는 동사로 나뉩니다. 동사는 보통 HTTP의 method를 나타냅니다.

URI + method를 사용한다면 현재 서비스중인 웹서비스들은 전부 REST?

라고 생각하면 안됩니다...

REST에서는 URI을 통해 리소스를 식별합니다.(보통 URI은 좌측에서 우측으로 읽어 갈수록 식별이 명확해집니다.) 그런데 URI에는 일련의 액션이 들어가지 않습니다. 이 말인 즉, 동사가 들어가지 않는다는 겁니다.
URI이 나타내는 리소스로 무엇을 할 것인가는 URL은 전혀 관심을 가지지 않지요.

그렇다면 리소스에 대한 조작은 어떻게 하느냐.
그것은 바로 동사를 나타낼 수 있는 method 와의 조합입니다.

  • URI === 리소스
  • METHOD === 액션

method 리스트를 보면, 우리에게 친숙한 GET, POST외에도 PUT, DELETE, OPTIONS, HEAD, TRACE 등이 있는데 이들의 특성을 정확하게 알아야 합니다.

대충 정리해보죠.

  • GET
    • 데이터를 조회합니다. 단순하죠.
  • POST
    • REST 리소스를 처리하는 리스닝 서버에 특정 작업을 데이터와 함께 전송하여 요청합니다. 메소드계에서 제일 반항적이고 네거티브한 녀석입니다.
  • PUT
    • 리소스에 데이터를 둡니다(?). GET과는 정확히 반대의 액션입니다. 보통 리소스의 조작등의 상황에서 사용된다고 볼 수 있습니다
  • DELETE
    • 리소스를 삭제합니다, 심플하군요.
  • OPTIONS
    • 서버 통신을 위해 사용할 수 있는 옵션을 요청합니다. 자세히는 잘 모르겠지만 쉘의 help 액션과 비슷한 용도일 듯 싶습니다.
  • HEAD
    • GET과 같지만 응답의 헤더만을 받습니다.
  • TRACE
    • 요청한 바디를 그대로 반환합니다. 응답 바디가 아니라 요청 바디입니다.

모든 method 에는 안전과 멱등 (Safety and idempotency)이라는 개념이 있습니다.
안전은 알겠지만 멱등이라는 말은 좀 어감이 웃기지 않습니까? (저만 그런가요...)

안전은 말 그대로 안전성입니다. method + URL 의 조합이 리소스의 상태를 변경하지 않으면 안전하다고 합니다.

멱등은 첫번째 요청 이후에 같은 리소스에 대해 반복되는 동사(method)가 어떠한 부작용도 일으키지 않는다면 멱등입니다. 그렇지 않다면 멱등하지 않습니다.

그렇다면 안전과 멱등으로 method를 분리해 봅시다.
위에서 GET, OPTIONS, HEAD, TRACE만이 안전 합니다. 리소스의 상태 변경을 하지 않습니다.

다음으로 멱등을 따져 봅시다.

GET은 멱등합니다. 자료를 조회할 뿐이죠.

POST는 멱등하지 않습니다.
POST는 위에도 썼듯이, method계의 반항아입니다. 안전하지 않음은 물론 멱등하지도 않아 다룰때 조심해야 합니다.

다들 웹 서핑하면서 뒤로가기 버튼을 애용하실 겁니다.
그런데 가끔 좋은 사이트(?) 의 경우 뒤로가기를 눌렀을 때 다음과 비슷한 안내가 뜰 때가 있습니다.



이런 경우 브라우저에서 자체 검열(?)한 method 경고입니다.
"방금 수행한 method는 멱등하지 않으니, 확인바람" 이라는 뜻이죠. 대부분 POST 수행을 다시한번 수행하려고 했을 때 일어납니다.

이런 장치가 없다면 인터넷에서 큰마음 먹고 화사한 봄 자켓을 결재한뒤 실수로 뒤로가기나 새로고침을 하는 순간 다시 결재정보가 서버에 전송되고, 두번 결재되는 불상사가 일어날 수 있겠지요.

POST는 안전하지도 멱등하지도 않기에 설계 시 주의가 필요합니다.
보통의 경우에는 POST를 받은 서버는 작업을 처리 후 새로운 주소로 클라이언트를 리다이렉트(HTTP 301...Location header 등으로) 시키는 처리를 하는 경우가 많습니다

PUT은 안전하진 않습니다. 리소스를 변경하기 때문이죠. 하지만 멱등합니다. 같은 리소스에 대해 PUT을 할 경우엔 당연히 몇번이 호출되도 같은 변경사항을 덮어 쓸 뿐이겠죠.

DELETE도 PUT과 마찬가지입니다. 안전하진 않지만 같은 리소스를 몇번이고 삭제해봤자 삭제는 한번뿐입니다.

OPTION, HEAD, TRACE는 일반적인 method가 아니고 메타성이며, 전부 안전하고 멱등합니다.


리소스의 표현과 RESTful한 표현은...
리소스는 변하지 않습니다.
하지만 표현이 리소스에 덧씌워진다면 형태만은 변경될 수 있습니다.

요청이 들어왔을 때 어떠한 형식으로 반환해야 하느냐는 보통 HTTP 요청 헤더의 Accept로 구별하는게 원칙인 듯 합니다. 하지만 헤더라는게 워낙 느슨한 정의에다가, 조작이 아주 쉬운 탓에 신뢰성이 없습니다.

보통은 여러 방법으로 어떠한 방법으로 리소스를 표현할지 정합니다.

ex) 요청의 반환 구현 예
  • Extension
    • .xml 이나 .html 등등.
  • Accept 헤더 ( + Content-type)
    • application/xml, text/plain 등등.
  • format parameter
    • URI의 쿼리스트링을 이용. datatype=xml 이런식.
(스프링을 공부하신 분이면 이게 ContentNegotiatingViewResolver 의 동작과 유사하다는 걸 아실 겁니다)

이제 RESTful 한 예제를 들어보죠.
RESTful 에 대한 반대의미로 RESTless 라는 표현도 쓰는걸 봤는데 재미있는 표현같습니다.

CASE A - RESTless
GET + http://www.example.com/blog/content/get_post.html?postNo=20

POST + http://www.example.com/blog/content/delete_post.html
Payload: postNo=20

POST + http://www.example.com/blog/content/new_post.html
Payload: post params...

CASE B - RESTful
GET + http://www.example.com/blog/content/post/20

DELETE+ http://www.example.com/blog/content/post/20

POST + http://www.example.com/blog/content/post/
Payload: post params...

둘의 차이가 느껴지시나요?

CASE A 에서는 URI에 동사, 즉 액션이 나타나 있습니다.
URI의 get_post~ 라든지, delete_post~...

이것은 저 URI이 액션을 포함하고 있어 극히 고정적이며 REST의 개념인 "URI === 리소스" 가 되지 않습니다. 게다가 표현 형식은 .html로 인한 HTML 표현만이 가능할 거라고 추측됩니다.
응답형은 전부 HTML이 될 거라고 추측됩니다.
이 경우 URL에 동사가 포함되어 있는데다 데이터의 표현도 한가지로 제한됩니다.
REST와는 좀 거리가 있네요.

CASE B 는 한 리소스 URI에 동사 method를 결합하여 다양한 작업을 수행할 수 있습니다.
리소스 표현또한 구체적으로 어떠한 행동이나 표현을 강제하고 있지 않습니다.
여기서 재미있는것은 POST의 요청인데, 말 그대로 POST할 위치를 현재로서는 알 수 없으므로 리소스의 구체적인 위치를 알수 없기에 아이디에 해당하는 숫자가 URL에는 포함되지 않습니다. 포스트 결과로 응답되는 데이터에 그 아이디가 반환되겠죠.

응답의 형식은 HTTP Status와 Content-type 헤더로 구분할 수 있겠군요.
예측을 하자면 GET은 HTTP Status로 200 (OK)과 컨텐트가, DELETE는 HTTP Status 로 204(No Content) 가 응답되며 코드의 내용처럼 컨텐트 반환은 없겠죠.
POST는 동작에 따라 301(Location - Redirect), 200(OK)나 201(Created), 그리고 응답으로 컨텐트가 있을 겁니다.
REST 하군요.


마치며
누군가 REST에 대해 이런 말을 했다더군요.

"단순하게 보이지만 결코 단순하지 않다."

2012. 3. 5.

흔한 코딩에서 찾는 DOM 자바스크립트 성능 향상법 몇개 (수정)

개요
자바스크립트는 인터프리터 언어입니다.
코드 인터프리터는 자바스크립트 코드를 실제 컴퓨팅 환경에 맞는 방식으로 바꿔 그때그때 실행해야 합니다. 인터프리터 언어는 일반적으로 컴파일 언어에서 해주는 코드 재배치나 최적화를 거의 하지 못합니다.

크롬에 탑재된 V8엔진JIT(Just-In-Time)은 자바스크립트계의 혁신일 정도로 성능을 끌어올렸는데요. 인터프리트 말고 직접 기계어 컴파일을 시도합니다. 그것에 자극받은 타 엔진들, 사파리는 니트로 엔진, 파이어폭스는 트레이스몽키라는 엔진을 도입했지요.

하지만 아직 이런 엔진을 믿고 컴파일 언어처럼 방만(?)한 코딩을 하기엔 시기상조가 아닌가 싶습니다.

몇가지 자주 쓰이는 코드 패턴에서 자바스크립트를 최적화할 방법을 찾아봅시다.


로직 스크립트의 위치는 <body> 태그 맨 뒷부분에
대부분의 웹 프로그래머는 외부 스크립트 선언을 <head> 태그에 모아 둡니다. 하지만 이건 대부분 좋지 않다는걸 알고 있는 프로그래머는 드문 것 같습니다.

브라우저는 HTML을 파싱할 때, 스크립트 태그를 만나면 화면 파싱을 중지하고, 먼저 스크립트를 해석하고 실행합니다. 스크립트 해석이 완전히 끝난 뒤에야 다음의 DOM을 다시 해석하고 그려나갑니다.

만일 <head> 에 아주 길고 복잡한 스크립트 태그가 있다면, 그 자바스크립트 코드를 실행하고 파싱하느라 사용자는 허연 화면을 보고 있게 됩니다. 굳어버린 화면을 보며 뒤로 가기를 누르게 될 수도 있죠.

특별히 페이지에 뭔가를 보여주기 전에 실행해야 할 스크립트가 아니라면 (이런경우는 거의 없을 겁니다) <body> 태그를 닫기 전, 그러니까 </body> 바로 앞에 두는 편이 좋습니다.

이렇게 하면 DOM 파서는 모든 화면에 표시할 스크립트를 해석하고, 화면에 전부 표시한 뒤, 마지막으로 화면에 적용될 스크립트를 해석합니다.
이렇게 해 두면 적어도 사용자가 아무것도 표시되지 않은 허연 화면만을 보고 있는 상황은 피할 수 있습니다.

그리고 대부분의 경우, DOM 객체 조작을 하려고 굳이 window.onload 이벤트 핸들러에 동작을 할당할 필요도 없어지는 효과도 있습니다. 이미 모든 엘리먼트는 파싱되어 로드된 상태이므로, 그냥 바로 접근하면 됩니다.

(추가 - 익명님 댓글)
대부분의 프레임워크는 이 문제를 해결하기 위해 defer 기능을 제공합니다. 이것을 지원하는 이유는
  • <body></body> 사이에 <script>를 작성하지 않기 위해서입니다. 
  • 유지보수를 할 때 <body>에서 <script>를 찾는 것이 번거러우며 다수를 사용할 때는 놓칠 수 있습니다.
  • DOM으로 엘리먼트를 생성하여 <body>에 첨부할 때 제어하기가 번거롭습니다. 전체 image가 로드된 것을 체크해야 완전하게 <script> 코드 실행을 보장할 수 있기 때문입니다.


전역 객체를 지역 객체로 복사하기
다음 코드를 보세요.
이미지들을 모두 링크 태그로 바꾸는 함수입니다.
function imageSrcToAncher () {
    var downloadList = document.getElementById("downloadList");    
    var images = document.getElementsByTagName("img");
    for(var i = 0; i < images.length; i++) {
        var a = document.createElement("a")
        a.href = images[i].src;
        downloadList.appendChild(a);
        document.removeChild(images[i]);
    }   
}
이런 코드는 일견 문제가 없어 보이지만, 자바스크립트 스코프 탐색상 전역 객체는 스코프 체인의 제일 마지막에 위치합니다. 

document는 전역 객체이며 이 함수에서는 전역 객체에 1 + n*2(이미지태그의 수*2) 만큼 접근됩니다. 만일 이 함수가 글로벌 컨텍스트에서 실행하는 함수라면 별문제가 없을 수도 있습니다. 

하지만, 이 함수가 중첩 함수이거나 객체의 메소드, 반환 클로저 등의 상황에서 동작하는 경우라면 이야기가 다릅니다. 

자바스크립트의 스코프 탐색 방법에 따라, 실행시의 변수 식별에서는 스코프 체인의 제일 첫번째부터 순차적으로 변수를 뒤져갑니다. 스코프 체인의 구성 규칙에 의해 전역 객체변수는 스코프 체인의 제일 마지막에 있게 되며 따라서 스코프 체인 탐색의 제일 마지막에 전역 객체변수에 접근하게 됩니다.

위 코드에서는 그것이 여러번 반복됩니다. 자연히 성능은 떨어지고 속도는 늦어집니다. 그리고 사용자는 역시 굳은 화면을 보고 뒤로가기나 새로고침을 연타할지 모르는 일입니다.

이런 경우는 다음과 같이 고쳐서 성능을 올릴 수 있습니다.
function imageSrcToAncher () {

    // 내부 스코프에 전역 객체 참조를 복사해둡니다.
    var doc = window.document;

    var downloadList = doc.getElementById("downloadList");    
    var images = doc.getElementsByTagName("img");    
    var len = images.length;
    for(var i = 0; i < len; i++) {
        var a = doc.createElement("a")
        a.href = images[i].src;
        downloadList.appendChild(a);
        doc.removeChild(images[i]);
    }   
}
간단한 코드 한줄로 성능은 이루 말할 수 없이 올라가고, 코드도 한결 깔끔해집니다.

(추가 Insanehong 님 댓글)
문서에 엘리먼트를 다수 추가할 때에는 DocumentFragment 를 사용하는 것이 좋다고 합니다. 일단 필요한 엘리먼트를 미리 DocumentFragment를 사용하여 만들어 두고, 문서에 한번에 추가할 경우, 문서의 요소 재배치와 표현 연산을 많이 줄일 수 있기 때문입니다.

(추가 - 익명님 댓글) 
var emptyEl = doc.createDocumentFragment();
브라우저에 따라 속성이 설정되지 않는 경우가 있으므로 생성하려는 엘리먼트와 속성을 살펴본 후 사용하는 것이 좋다고 합니다.

innerHTML 사용 및 문자열 결합 주의
innerHTML은 참 편한 Element의 프로퍼티입니다. 하지만 잘못된 사용은 성능 저하를 가져옵니다.
var console = document.getElementById("console");
var lis = document.getElementsByTagName("il");
var len = lis.length;
for(var i = 0; i < len; i++) {
    console.innerHTML += li[i].innerHTML + "<br />";
}
위 코드는 innerHTML 속성을 무분별하게 참조합니다.
DOM 객체에 접근하는건 생각하는 것 이상으로 많은 자원을 소모하는 일입니다. 자바스크립트는 자바스크립트 코어에서 이웃집인 DOM API에 계속 방문하여 innerHTML을 찾는 거라고 보면 됩니다.

위 코드는 루프가 돌 때마다 3번씩 innerHTML을 참조하고 있습니다.
1번은 console의 innerHTML을 읽고, 그다음은 li의 innerHTML을 읽은 뒤, 마지막으로 console의 innerHTML에 합친 속성값을 할당하기 위해 읽습니다.

굉장히 비효율적입니다.

아래처럼 고치면 더욱 효율적입니다.
var console = document.getElementById("console");
var lis = document.getElementsByTagName("il");
var len = lis.length;
var cache = [];
for(var i = 0; i < len; i++) 
    // 일반적으로 이러한 문자열 결합은 +로 더해가는 것보다,
    // 배열을 사용하여 전부 요소로 등록하고 join을 쓰는 것이 빠릅니다.
    cache.push(li[i].innerHTML);
}
console.innerHTML = cache.join("<br />");
이렇게 고치면 루프 당 한번씩만 innerHTML을 참조하게 되어 효율이 올라갑니다.

(추가 아웃사이더 님 댓글)
최근의 모던 브라우저들은 + 문자열 결합을 최적화해주기 때문에 Array.join보다 좋은 성능을 보여주고 있다고 합니다.

(추가 - 익명님 댓글)
innerHTML이 표준이 되었기에, 문자열을 조합하여 한 번에 innerHTML을 실행하는 것도 하나의 방법이 된다고 합니다.

(추가 - 익명님 댓글)
문자열 결합 부분을 단정적으로  +, Array.join() 중 어느 방법이 낫다고 판단할수 없습니다.
아웃사이더 님의 댓글에서 제시된 사이트의 테스트 환경은 제대로 된 테스트 조건이 아니었기 때문이지요.
Array.join()은 모든 연결시킬 문자열을 배열에 다 넣은 다음 join()문을 단 한번만 실행하는게 핵심입니다. 하지만 벤치 테스트 사이트에서는 문제의 join() 문장을 매번 호출하면서 테스트하는 모습을 볼 수 있습니다. 이는 결코 이상적인 상황이 아니며 속도가 빨리 나오는거 자체가 이상합니다.

거기에 실제 코드 작성 상황에선

반복문{
    s += "문장" + "문장" + "문장";
}
의 형태가 자주 나온다는 사실도 고려해야 합니다.

벤치 테스트에서 + 가 성능이 좋게 나온 이유는 아마도 브라우저 인터프리터가 한 문장에 +문이 연속되어 있는경우 처음에 모든 문자열을 담을 수 있는 공간을 만든 다음 한번에 입력해서 성능이 향상되는걸로 보이지만, 실 상황에선 조건문과 함수로 인한 추가 문장 등, 위와 같은 상황이 생기기 힘든 상황이 자주 발생하게 됩니다.

(개인적 의견...)
테스트 사이트에서 루프 밖 조인을 한다고 되어 있는 듯 합니다.
(testing for concat vs join outside of a loop)

그리고

반복문{
    s += "문장" + "문장" + "문장";
}

의 결합은 대부분 브라우저에서 다른 결합들 (순차 += 결합, join 등...) 보다 빠른 속도를 내는 것 같습니다.

타이머를 사용한 루프 최적화
루프문은 프로그래밍 사용시 거의 반드시라고 해도 좋을 정도로 등장하는 코드입니다. 제일 성능 이슈가 많이 일어나는 곳이기도 하죠.

자바스크립트도 예외가 아닌데요.

만일 이러한 코드가 있다고 봅시다.
imageSoruce 변수에 배열 형식으로 이미지 태그의 src 속성이 담겨 있다고 가정해 봅시다. 이것을 활용하여 특정 영역에 받아온 src 정보를 사용하여 이미지 태그를 그리는 로직입니다.
var doc = document;
var imageSlide = doc.getElementById("image_slide");
var len = imageSoruce.length;

for(var i = 0; i < len; i++) {
    var img = doc.createElement("img");
    img.src = imageSoruce[i];
    imageSlide.appendChild(img);
}
적은 양의 이미지라면 상관 없을 듯 싶습니다.
그러나 문제는 언제나 루프가 비대해질 경우입니다. 표시해야할 이미지가 많다면 이 코드는 전부 실행되기 전까지 화면은 일체의 사용자 인터랙션을 거부합니다.

즉, 화면이 굳습니다. 
아무것도 동작하지 않고 화면에 받아온 데이터의 끝까지 루프를 돌면서 이미지 태그를 전부 그릴 때까지 화면은 이무것도 하지 않습니다. 스크롤도 안되고 클릭도 안됩니다.

이런 상황은 별로 좋지 않습니다...

타이머를 사용하여 루프를 최적화해 봅시다.
자바스크립트의 대표적인 타이머 함수는 setTimeoutsetInterval이 있습니다.
이중 setTimeout을 사용해 루핑 중 화면이 굳어버리는 불상사를 막아봅시다.
var doc = document;
var imageSlide = doc.getElementById("image_slide");

// 배열을 복사합니다.
var copy = imageSoruce.slice();

// 타이머를 사용하여 재귀호출로 자료를 처리합니다
setTimeout(function() {    
    var img = doc.createElement("img");

    // 복사한 배열에서 요소 하나를 가져옵니다.
    img.src = copy.shift();
    imageSlide.appendChild(img);

    // 배열에 남은 요소가 있다면 다시 호출합니다.
    if(copy.length > 0) {
        setTimeout(arguments.callee, 25);
    }
    else {
        // 작업 완료 작업...
    }
}, 25);
이런 방식으로 타이머를 사용하여 루프를 만들면 각 작업이 UI 큐에 쌓이게 되면서 순차적으로 처리됩니다. 물론 중간에 사용자의 인터랙션이 오게 되면 그 작업도 큐에 쌓이면서 처리됩니다.

화면이 굳는 것을 막을 수 있을 뿐 아니라, 좀더 코드를 추가하여 이미지를 순차적으로 로딩하는 멋진 UX를 제공할 수도 있습니다.


참고자료
이 포스트에는 자바스크립트 성능 최적화 를 많이 참고하였습니다.
자바스크립트 프로그래머라면 반드시 읽어야 할 좋은 책이니, 만일 읽어보지 못한 자바스크립트 공부를 하시는 분이 계시다면 한번 읽어보는걸 추천드립니다.

2012. 3. 1.

HelloWorld 오픈세미나 자료들



스프링 AOP의 지시자 this와 target

스프링 AOP에서 포인트컷 표현식 지시자 문법 중 this와  target이 혼동이 올 때가 있습니다.
(저만 그럴지도;)

둘다 하나의 타입을 지정하는 방식이지만 동작은 다소 차이가 존재합니다. 두개의 큰 차이이자 확실한 구분점은 타겟과 프록시의 기준입니다.


  • this는 프록시 빈 자체의 타입으로 적용될 타겟을 선별
  • target은 타겟의 타입으로 적용될 타겟을 선별


예제를 봅시다.
아래와 같은 인터페이스와 클래스가 있다고 가정합니다.
package me.javarouka;
public static interface World {}

package me.javarouka; 
public static class Hello implements World {}
이 경우 this의 포인트컷
@Pointcut("this(me.javarouka.Hello)")
public void detectThis1(){}
은 Hello에 어드바이스를 하지 못합니다. 프록시 빈은 Hello 타입이 아니기 때문입니다. 인터페이스만 같을 뿐, 서로의 타입은 전혀 다릅니다.

프록시 빈의 타입은 World입니다. 그렇다면,
@Pointcut("this(me.javarouka.World)")
public void detectThis1(){}
로 지정해야 합니다.
이 경우에는 프록시는 World 타입이며, 적용됩니다.
주의할 점은 AOP 설정에서 proxy-target-class 등으로 인터페이스가 아닌 타겟에 대해 프록시를 생성하게 해 두었다면 위의 detectThis1 포인트컷도 적용됩니다. 주의하세요.

반면 target 지시자는 다릅니다. 타겟의 타입으로 포인트컷을 선별합니다.
타겟이 중심이 되며, 타겟 자체의 타입이 기준이 됩니다.
그래서 아래의 포인트컷 둘다 Hello 에 적용됩니다.
@Pointcut("target(me.javarouka.Hello)")
public void detectTarget1(){}

@Pointcut("target(me.javarouka.World)")
public void detectTarget2(){}

2012. 2. 28.

자바스크립트 패턴 #3 - 플라이웨이트 패턴 (Flyweight Pattern)

자바스크립트 라이브러리 레벨에서 자주 쓰이는 플라이웨이트 패턴(Flyweight Pattern)은 사용시 굉장한 자원의 절약과 메모리의 낭비를 막아줍니다.

자바스크립트는 따로 C나 Java같은 컴파일 과정이 없어(현대식의 자바스크립트 엔진, 가령 구글 크롬의 V8엔진의 경우 컴파일 비슷한 과정과 재정렬을 한다고 들었던 것 같습니다만) 프로그램 시 프로그래머가 직접 컴파일러가 해주는 코드의 효율화에 더욱 신경써야 합니다.

플라이웨이트 패턴이란 프로그래밍시에 많은 수의 유사성을 가진 객체들이 생성되어질 때, 그 객체끼리는 공통된 부분과 공통되지 않은 부분을 분리하여 관리하여 공통되는 부분을 객체들이 공유하도록 하는 패턴입니다.

그리고 한번 생성된 공유 객체는 저장하여 재생성을 (풀링이나 캐싱 등으로)막아 메모리 사용을 줄입니다.

  • 공유되는 부분은 이미 생성되었다면 생성된 것을 공유하고, 아니라면 생성하고 공유함.
  • 한번이라도 생성한 객체는 다시 생성하지 않음

이때 객체를 생성하는 방법으로 팩토리를 이용하는 것이 일반적이지만, 객체 자신이 제공하는 특정 메서드를 통해 직접 공유 자원을 자신이 참조하는 방식이 되기도 합니다.

말로 하니까 굉장히 어렵습니다.

일단 어떠한 방식으로 구현하더라도 다음과 같은 개념들을 구현하게 됩니다.

Factory
객체를 제공하는 팩토리 구현입니다. 프로그램에서 쓰일 객체들을 공급합니다.

Intrinsic
객체들이 공유하는 본연의 특성들을 모은 것입니다.
공유되는 부분이죠.
간혹 불변(immutable) 이 되기도 합니다.

Extrinsic
Intrinsic 부분 외의 서로 다른 외부 특성입니다.

플라이웨이트 패턴에서 제일 많이 예제로 표현되는 것은 문서 편집기의 구현입니다.
편집기 구현때 문자 객체는 문서가 몇페이지가 넘어가든 단 1개만 생성되고, 그것에 대한 참조만을 가지고 위치를 달리하여 문서를 구성하게 됩니다. 실제 문자 하나하나 생성하다간 문서가 몇페이지 넘어가지 않고 Out of Memory를 볼 수 있겠지요.

또 다른 하나는 Java 언어의 스트링(String) 구현입니다.
스트링이 기본 타입으로 생성될 때, (즉 String h = "hello" 같은) JVM은 특정한 내부 팩토리 함수를 호출합니다. 팩토리 함수는 자신이 가진 플라이웨이트 사전을 뒤져 이미 있다면 그것을 리턴하고 없다면 내부적으로 스트링을 생성하여 사전에 넣고 리턴합니다.
// 두 String은 변수 같은 String 인스턴스를 참조한다.
String msg1 = "Hello world1";
String msg2 = "Hello world1";

if(msg1 == msg2 && msg1.equals(msg2)) {
    System.out.println("두 문자열은 완전히 동일하다");
}
String 객체는 훌륭한 플라이웨이트 패턴 구현이지요.

이제 자바스크립트 코드로 구현해 봅시다.

친숙한 게임인 스타크래프트를 예로 들어보겠습니다.
A 유저와 B 유저가 대결합니다. 그리고 게임이 시작되었고 서로 같은 종족인 저그를 선택했다고 가정합니다.

유닛의 구조를 다음과 같이 잡아봅시다.
var Unit = function(name, maxhp, cost, player, upgrade) {
    this.name = name;
    this.maxhp = maxhp;
    this.cost = cost;
    this.player = player;
    this.upgrade = upgrade;
    this.curentHp = maxhp;
}
Unit.prototype = {
    move: function() { ... },
    attack: function() { ... },
    defence: function() { ... }
}
서로의 유닛은 같은 객체입니다.
A가 생산한 히드라리스크와 B가 생산한 히드라리스크는 유닛이름, 최대체력, 생산비용이 같겠지요. 게임은 공정해야 하니까요.

이 부분은 그럼 공통 속성 (Intrinsic) 이 됩니다.

하지만 조종하는 플레이어와, 그 플레이어가 적용한 업그레이드 레벨은 다를 것입니다.
이 부분은 외부 특성 (Extrinsic)이 됩니다.

본격적으로 패턴을 적용해 봅시다
외부 특성은 빼고 공통 속성만 남겨보면 다음과 같이 됩니다.
var Unit = function(name, maxhp, cost) {
    this.name = name;
    this.maxhp = maxhp;
    this.cost = cost;    
}
이제 공통 속성을 관리해주면서 유닛을 생산할 팩토리 객체를 만듭니다.
모듈 패턴을 사용합니다.
var UnitCreator = (function() {
    
    // 한번 생성한 객체는 다시 생성하지 않기 위한 저장소
    // private 이다.
    var units = {};

    // 모듈 패턴을 사용한 public 속성들
    return {
        create: function(name, maxhp, cost) {
 
            var unit = units[name];

            // 저장소에 이미 있다면 그것을 리턴
            if(unit) {
                return unit;
            }

            // 없다면 생성하고 저장소에 저장
            else {
                units[name] = new Unit(name, maxhp, cost);
                return units[name];
            }
        }
    } 
    
})();
이제 게임에서 유닛의 조종자와 각종 기능을 수행할 컨트롤러를 만듭니다.
var UnitController = (function() {
    
    // 플라이웨이트 객체를 관리할 맵
    // 역시 private 속성.
    var unitsPlayedDictionary = {};

    var self = this;

    // 모듈 패턴을 사용한 public 속성들
    return {
        produceUnit: function(id, name, maxhp, cost, player) {
            
            var unit = UnitCreator.create(name, maxhp, cost);
            
            // 공유 자원과 객체 특성을 조합 (컴포지트 패턴)
            var unitOnPlay = {
                unit: unit,
                id: id,
                player: player,
                currentHp: unit.maxHp,
                upgrade: 0                
            };
            unitsPlayedDictionary[id] = unitOnPlay;
        },     

        kill: function(id) {
            var u = unitsPlayedDictionary[id];
            if(unit) {
                u.currentHp = 0;
                delete unitsPlayedDictionary[id];
            }
        },
        
        upgradePower: function(id) {
            var u = unitsPlayedDictionary[id];
            if(unit) {
                u.upgrade = u.upgrade + 1;
            }
        },
        
       /* 기타 메서드들 ... */
    }    
    
})();
위에 든 예제가 좀 허접합니다. 기회가 된다면 좀더 좋은 예제를 들어 설명해보고 싶네요.

패턴을 잘 살펴보면 몇가지 패턴이 모여 플라이웨이트 패턴을 만드는 것을 볼 수 있습니다. (모듈 패턴, 컴포지트 패턴)

유명 자바스크립트 라이브러리인 ExtJs, jQuery는 이 플라이웨이트 패턴을 적극적으로 활용하여 속도 및 메모리 효율성을 끌어올리고 있습니다.

공통된 속성은 라이브러리가 제공하는 유용한 기능이 되겠지요.

플라이웨이트 패턴은 분명 컴퓨터 입장에서는 효율이 좋습니다.
그러나 프로그래머 입장에서는 굉장히 불편한 패턴입니다. 과용시 코드의 가시성이 굉장히 떨어지며 유지보수가 힘든 스파게티 코드가 될 위험이 많습니다.

디자인 패턴이란 언제나 과용은 금물이죠;

포스팅하며 참고한 사이트들입니다.

2012. 2. 27.

자바스크립트 패턴 #2 - 모듈 패턴 (Module Pattern) (수정)

모듈 패턴은 전통적인 소프트웨어 공학에서 클래스 사용에 private 과 public 으로 나뉜 캡슐화를 제공하는 방법이다.

자바스크립트에서의 모듈 패턴은 전역 영역에서 특정 변수영역을 보호하기 위해 단일 객체 안의 public/private의 변수를 포함할 수 있는 각 클래스 형식의 개념을 구현하는데 사용된다. 이 패턴으로 당신이 페이지에 추가한 추가적인 자바스크립트가 다른 스크립트와 이름이 충돌하는 것을 줄여줄 수 있다.


(발번역이지만 내용 전달은 되었길 빕니다;)

자바스크립트는 private, public 등의 접근 제한자를 언어차원에서 지원하지 않습니다.
하지만 모듈 패턴을 사용하여 그런 접근 제한을 구현해낼 수 있습니다. 요새 CommonJS, AMD (Asyncronouse Module Definition) 등의 자바스크립트의 표준화의 진행은 자바스크립트의 모듈화에 주안점을 두고 있지요. 그 근간은 이 모듈 패턴입니다.

고급 사용자라면 더욱 미려하고 시적인 모듈 패턴을 구사할 수도 있겠지만 저의 지식이 그에 못미치는 관계로 기초중에 기초만 설명해볼까 합니다.

모듈 패턴을 이해하려면 클로저와 컨텍스트에 대한 지식이 조금은 필요합니다.

그럼 간단한 코드를 예로 들어보죠.
서버에서 데이터를 얻어와 반환하는 코드의 한 예시입니다.
이때 한번 요청한 데이터는 캐싱되어야 합니다.
// 데이터 캐시
var dataCache = {};

// 데이터 캐시 아이디
var id = 0;
    
var url = '/default/data';
// ... 기타 사용 변수

var connectServer = function() { ... }
var sendRequest = function() { ... }
var parseData = function(data) { ... }
var getData = function() {
    connectServer();
    var data = sendRequest();
    dataCache[id++] = data;
    return parseData(data);
}
위와 같은 코드는 물론 잘 동작합니다.
하지만 전역 공간에 변수가 무분별하게 선언되어 있습니다. 이는 위에서 모듈 패턴의 정의에서 말한것과 같이 추가적인 스크립트(외부 라이브러리든 다른 개발자에 의해) 가 있을 경우 이름이 충돌할 수 있습니다. 함수 이름도 getData같은 아주 흔한 이름이기에 출동 가능성은 더욱 높습니다.
(혹시 이를 대비해 함수 이름을 어렵고 길게 만들자는 생각을 하신분은 없으시겠죠)

그리고 모든 변수들은 public 접근제한 상태입니다.
connectServer,  sendRequest,  parseData 이 세개는 특별한 경우 외엔 다른 곳에서 쓰일 필요가 없습니다.

private 접근제한이 적당할 것 같습니다

전역에 변수를 선언하고 싶지 않으려면 다음과 같이 익명 함수를 통한 선언을 해 볼 수 있습니다.
// 데이터 캐시
var dataCache = {};

// 데이터 캐시 아이디
var id = 0;

// 익명 함수로 감싸 전역 객체를 더럽히지(?)
// 않는다.
// 하지만 여전히 캐시는 전역에...
(function() {
    
    var url = '/default/data';
    // ... 기타 사용 변수
    
    var connectServer = function() { ... }
    var sendRequest = function() { ... }
    var parseData = function(data) { ... }
    
    var getData = function() {
        connectServer();
        var data = sendRequest();
        dataCache[id++] = data;
        return parseData(data);
    }
    
    getData();
    
})();
자. 어떻습니까. 모든 변수들이 private 스코프가 되었군요. 익명 함수로 인해 전역 스코프 접근도 없게 되었습니다.
그런데 이런 방식으로는 코드의 재사용을 전혀 할 수가 없습니다. 매 데이터 요청 시 저런 긴 코드를 쓸 생각이 아니라면 좀더 생각해 봅시다.
// 데이터 캐시
var dataCache = {};

// 데이터 캐시 아이디
var id = 0;

// 별도의 네임스페이스 적용. 
// 역시 캐시는 밖에 있지만 함수는 재활용이 가능하다.
var getData = function(url) {        
    
    url = url || '/default/data';
    // ... 기타 사용 변수
    
    var connectServer = function() { ... }
    var sendRequest = function() { ... }
    var parseData = function(data) { ... }
    
    connectServer();
    var data = sendRequest();
    dataCache[id++] = data;
    return parseData(data);
}
이건 나름 좋은 방법 같습니다.

필요한 함수들이 private되어 감춰졌습니다.

최소한으로 전역 영역에 자신을 노출하면서 기능을 재사용할 수도 있습니다. GetData라는 것은 일종의 네임스페이스 역할을 한다고 볼 수 있겠네요.그러나 아직 부족합니다.

저 개념을 좀더 발전시켜 봅시다.

데이터를 일관된 방식으로 요쳥하고 데이터 파서를 지정할 수 있는 객체를 제공하는 모듈을 만듭니다.
그리고 같은 요청시의 캐시 문제도 해결해 봅시다.
var spec = {
    url: '/some/path/data',
    callback: function(data) { ... }, // 콜백 지정
    parser: function jsonParser(data) { ... } // 파서 지정
};
// 모듈화. 생성 인자로 객체를 받는다.
// spec 객체를 바탕으로 객체 생성.
var dataModule = (function(spec) {
    
    // private 영역 시작

    // 데이터 캐시
    var dataCache = {};
    
    // 데이터 캐시 아이디
    var id = 0;
    
    var url = spec.url || '/default/data';
    // ... 기타 사용 변수
    
    var connectServer = function() { ... }
    var sendRequest = function(opt) { ... }
    var parseData = spec.parser || function(data) { ... };
    
    var callback = spec.callback || function() { };    
    var headers = spec.headers || {};

    // private 영역 끝.
    
    
    // 필요한 것만 공개. 접근 제한은 public이 된다
    // 리턴되는 객체의 메서드들은 클로저로서
    // private 영역의 변수에 접근이 가능하다.
    return {
        send: function() {
            connectServer(spec.url, spec.method);
            var data = sendRequest(headers);
            dataCache[id++] = data;
            return parseData(data, callback);
        },
        cache: function(id) { return dataCache[id]; },
        getLastCacheId: function() { return id; }
    } 
    
})(spec); // 익명 함수를 바로 실행

// @Test 코드
// 데이터 요청
var rs = dataModule.send();
console.log(dataModule.getLastCacheId()) // 마지막 요청 아이디

무명 함수의 결과를 받는 객체의 이름은 뭘로 지정해도 상관 없습니다. dataModule로 지정해 두었을 뿐 다른 프로그래밍시에는 다른 이름이 될 수도 있겠죠.

모듈은 별도의 정의된 이름 공간에 두면 문제가 없습니다. 한번 사용하고 말 것이라면 내부에 선언하고 바로 처리해도 되겠지요.
모듈 패턴은 사용하기에 따라 굉장한 용도가 있습니다.

어떻게 보면 new 를 사용한 객체의 생성보다 더욱 자바스크립트스러운 객체 생성방법이라고도 생각됩니다.

영어에 자신이 있으신 분은 고수가 쓰신 이 포스팅을 읽어보시길 추천드립니다.

Step by Step :: 토비의 스프링 3.0 - 명품 설명이란 이런것

"난 스프링에게 고마움을 느낀다. 스프링을 통해 이전보다 더 나은 개발자가 됐다고 느끼기 때문이다."
- 저자 Toby (이일민)



스프링은 이제 자바프로그래머의 기본 소양 정도로 되어가는 듯 싶습니다.

타국에서는 어떤지 모르겠지만 적어도 한국에서는 좀 과장하면 자바를 한다면 스프링은 반드시 알아야 자바프로그래머라고 명함을 겨우 내밀 수 있을 정도입니다.

보통 전문 서적은 해외가 빠르기에 한국에도 스프링 번역서가 몇권 소개되었지만 번역체의 남발이나, 어설픈 예제 코드, 이해가 힘든 설명등으로 좌절했던 사람도 많을 겁니다.

이 책은 그런 걱정은 안하셔도 될 것 같습니다. 이 책이 나왔기 때문이죠.

책 저자가 한국인이고, 예제는 진짜 기초적인 코드로 고급 스프링의 개념을 설명하고 있습니다.

다른 책처럼 다짜고짜 스프링을 처음부터 클래스패스에 구겨넣고 설정은 이러저러합니다 라는 식으로 "갖다 쓰는 법"을 설명하지 않습니다.
먼저 스프링을 어떻게 사용하게 되는지, 사용하면 뭐가 좋은지, 사용에 있어 스프링이 어떤 방식으로 돌아가는지를 설명합니다.

1부에서는 스프링의 적용을 착실히 계단식으로 설명합니다.
신입 사원도 만들지 않을 듯한 초난감한 UserDao 클래스를 일단 독자에게 던져 두고, 그 코드를 계단식 방법으로 차근차근 검토하며 리팩토링을 수행하면서 그곳에서 나온 문제들을 스프링에서는 이런 식으로 해결할 수 있다는 방식이 이 책의 주된 설명 법입니다.

  • 원시적인 문제 해결 - 단순한 방법...메서드 분리 등
  • 어설픈 방식의 문제 해결 - 디자인 패턴 적용 등
  • 나름 수려한 문제 해결 - 패턴 적용의 고급화 등
  • 스프링 적용 문제 해결 - 위 처리가 내장된 스프링으로 문제해결 보완
의 단계를 순차적으로 설명합니다.

절대 먼저 스프링 갖다 붙이지 않습니다. 이해한 뒤에 사용법을 설명합니다.

저 자체도 서블릿을 모르고 스트러츠나 스프링을 갖다쓰는 프로그래밍에 회의적인 터라 이러한 이 책의 설명 방식에 완전히 매료되었습니다.

뭔가에 대해 설명할 때 알기 쉽게 편하게 설명한다는 것은 좀 안다의 수준을 넘어 그것에 대해 이미 즐길 정도로 알아야 하는데, 건방지게 제가 감히 평가하자면 이 책의 저자인 토비님은 그 경지에 오른 분 같습니다. 


2부에서는 실제 활용법과 공부에 대해 좀 더 자세히 설명합니다.
2부의 제목도 "선택" 입니다.

각종 스프링의 설정과 애노테이션 활용, @MVC등...
여러 예제를 통해 다양한 기술적 선택지를 설명하지만 책의 저자분은 절대 뭔가에 대한 선택은 프로그래머에게 맡깁니다.

이 역시 다른 책에서 흔히 보이는 외길 설명과 차별화되는 부분입니다.

또한 이 책은 개발에 있어 테스트의 중요성을 굉장히 강조하며, 테스트를 위한 장이 따로 할당되고 전반부 부분의 예제 소스에 대해서는 철저한 테스트 소스까지 작성해가는 법을 설명하며 테스트를 설명합니다. 이정도까지...? 라고 할 정도의 테스트 설명이지만, 읽다 보면 아! 이런 유연한 테스트가! 라며 감탄하실 수 있을 거라고 생각됩니다.

독자의 이해를 돕기 위해 중요한 부분은 중복해서 설명을 여러번 해주기도 합니다. 두께에 놀랄 필요 없습니다. 집중해서 읽는다면 1부(전반부) 부분은 쉽게쉽게 장이 넘어가기 때문이니까요.

이 책의 단점은 다소 비싼 가격과, 1400페이지가 넘는 볼륨에 자칫 읽기 어려워 보인다는 것 빼고는 없다고 생각되네요.

책을 쓴 저자님이게 감사 말씀 올립니다. 읽을 때마다 배우는 책인 것 같습니다.

저자 블로그 후기 : 토비의 스프링이 나오기까지

2012. 2. 26.

URL의 쿼리스트링 가져오기

얼마전 자바스크립트로 URL 쿼리스트링을 파싱하는 방법에 대해 질문을 받아 포스팅으로 올립니다.

쿼리스트링은 DOM 객체중에 location의 search 속성을 사용하여 얻어올 수 있습니다.
// 만일 
// http://example.com/index.html?say=Hello+World+!&name=javarouka&mail
// 이라면
var queryStrinfg = window.location.search;

// 결과는 '?say=Hello+World+!&name=javarouka&mail'
console.log(queryStrinfg);
쿼리스트링은 키와 값이 '=' 로 구분되어 있고 그 셋이 '&' 로 나열됩니다.

그렇다면 정규표현식이나 split 함수를 사용하여 파싱할 수 있습니다.
주의할 점은 반드시 값에 URLDecode가 필요하며, 공백 문자는 '+' 로 대체되니 주의해야 합니다.

먼저 '+' 를 공백으로 교체하고, 나머지 문자열에 대해 URLDecode을 적용하면 괜찮을 것 같습니다.

아래 코드에서는 정규표현식을 쓰는 방법을 사용했습니다.
그럼 아래 코드로 풀어봅시다.
var QueryObject = function() {

    var o = {};

    var q = location.search.substring(1);
    if (q) {

        // 실제 그룹화 정규식.
        var vg = /([^&=]+)=?([^&]*)/g;
        
        // 인코딩된 공백문자열을 다시 공백으로
        var sp = /\+/g;

        // 정규식을 사용하여 값을 추출
        var decode = function(s) {
            if (!s) {
                return '';
            }
            return decodeURIComponent(s.replace(sp, " "));
        };

       // 한번씩 exec를 실행하여 값을 받아온다.
        var tmp; 
        while (tmp = vg.exec(q)) {
            (function() {
                var k = decode(tmp[1]);
                var v = decode(tmp[2]);
                var c;
                if (k) {
                    o[k] = v;
                    // getXXX 형식의 자바빈 타입으로 사용하고 싶다면
                    // 윗 라인을 지우고, 아래와 같이 하면 됩니다.
                    //c = k.charAt(0).toUpperCase() + k.slice(1);
                    //o["get" + c] = function() { return v; }
                    //o["set" + c] = function(val) { v = val; }
                }
            })();
        }
    }
    return o;
};

2012. 2. 21.

자바스크립트 패턴 #1 - 싱글톤 패턴 (JavaScript's Singleton Pattern) (수정)

자바스크립트의 함수는 new 로 생성자로 사용할 때마다 새로이 생성된 객체를 리턴합니다.

하지만 특수한 상황에서는 하나의 함수에서 생성되는 객체가 오직 한개만을 가져야 할 때가 있습니다. 그럴 경우 사용되는 디자인 패턴이 Singleton Pattern 입니다.

보통 하나의 객체를 전역적으로 공유해야 할 때 많이 쓰이지요.

자바스크립트에서는 그냥 전역영역에 객체를 하나 생성해서 두면 되지만 전역 객체의 사용은 매우 안좋은 코딩이며 절대 사용해서는 안되는 것입니다.

Java의 경우에는 생성자를 private 로 지정하고 별도의 스태틱 팩토리 메서드를 통해 인스턴스를 반환받는 형식으로 구현됩니다. 아래 처럼요.
public class JavaSingleton {

    // private 접근제한으로 객체를 미리 생성.
    // 간혹 생성자에서 늦은 초기화로 생성하기도 하나,
    // 객체의 실제 생성시간 차이로 인한 멀티스레딩 오류가 있으므로 비추천.
    private static JavaSingleton ME = new JavaSingleton();

    // private 생성자
    private JavaSingleton() { }

    // 스태틱 팩토리 메서드.
    // 언제나 하나의 객체만 리턴.
    public static JavaSingleton getInstance() {
         return  JavaSingleton.ME;
    }
}

public class JavaSingletonTester {
    public static void main(String [] args) {
     
        // 일반적인 new 생성 불가능.
        // JavaSingleton single = new JavaSingleton();

        // 두 객체는 동일.
        JavaSingleton ins1 = JavaSingleton.getInstance();
        JavaSingleton ins2 = JavaSingleton.getInstance();   

    }
}
자바스크립트의 경우 위 방법으로는 구현이 되지 않습니다.
명시적인 private 선언도 없고, 저런 생성자 자체를 막는 방법도 존재하지 않기 때문이죠.

다른 방법을 생각해봐야 합니다.

만일 함수가 생성자로 호출된다면 자신이 생성할 객체를 따로 가지고 있다가, 재차 호출 시 그 객체만을 반환하게 하는 방식을 생각해 봅시다.
이러한 방식이 될 것입니다.
// 싱글톤을 생성해주는 모듈 객체. 
// 익명 함수 실행 결과를 받습니다.
var SingletonTester = (function(){
    
    // 실제 싱글톤 적용 객체
    function Singleton(args) {
        
        // 내부 작업...
        var args = args || {};
        this.a = args.a;
        this.b = args.b;  
    }

    // 인스턴스 객체. 
    // 다수의 객체 생성을 제한하는 역할입니다
    var INSTANCE;

    // 외부에 공개될 객체를 반환합니다.
    // 모듈 패턴(Module Pattern)이라고 부릅니다
    return {        
        getInstance: function ( args ){
            if (INSTANCE === undefined) {
                instance = new Singleton( args );
            }
            return INSTANCE;
        }
    };
    
})(); // () 연산자로 선언과 동시에 바로 실행.

// 테스트
var singletonTest = 
    SingletonTester.getInstance( { a: "hello" } );
singletonTest.a; // a 
 - 위 코드는 Essential JavaScript Design Patterns For Beginners 를 참고 하였습니다.
위 방법대로 하면 getInstance를 사용하는 한 언제나 동일한 객체를 얻어낼 수 있습니다.
여기서 좀더 욕심을 부려 봅시다.

위 방식은 싱글톤을 적용할 객체마다 위의 코드를 써줘야 한다는 단점과 반드시 getInstance 함수를 사용하여 객체를 얻어내야 한다는 번거로움이 있고, 실수로라도 new 연산자를 사용할 경우 엉뚱한 객체가 얻어지기도 합니다.

위의 단점을 해소한 재사용이 가능하며, new를 쓰든 getInstance를 사용하든지간에 싱글톤이 적용가능하도록 코드를 약간 다르게 구현해 봅시다.

재사용하려면 몇가지 고려가 필요한데, 함수를 전달하여 그것을 싱글톤 생성자로 바꿔주려면 인자를 유연하게 넘겨줘야 하며, 동적으로 getInstance 함수도 지정해줘야 합니다.
그리고 private으로 관리되는 유일 객체 변수도 가져야 합니다.

생성자의 인자 전달 문자는 arguments 함수와 apply를 사용한 기법으로, 유일 객체 변수는 클로저화 시키는 방법밖엔 없는 것 같습니다.

아래 코드로 가기 전에 자바스크립트에서 new 연산자의 동작을 한번 봅시다.
  1. 생성자의 프로토타입을 연결한 새 빈 객체를 생성한다.
  2. 전달된 인자와 함께 생성된 객체를 컨텍스트로 함수를 실행한다.
  3. 함수 실행 결과가 객체이면 그 객체를 리턴하고, 아닐 경우 위에서 생성한 객체를 리턴한다.
아래는 코드.
/* 
    @name Singletonify - By javarouka (MIT Licensed)
*/
var Singletonify = function(cons) {
    
    // 유일 객체 변수
    var INSTANCE;
    
    // 클로저 생성
    var c = function() {
        // 유일 객체가 정의되지 않았다면 객체를 생성.
        if(INSTANCE === undefined) {
            
            // 여기서부터 new 연산자의 내용을 흉내냅니다.
            
            // 새 함수를 선언하고 인자로 전달받은 함수의 프로토타입으로 연결합니다.
            var F = function() {};
            F.prototype = cons.prototype;
            
            // 객체를 생성하고 생성된 객체를 컨텍스트로 호출합니다.            
            var t = new F();
            var ret = cons.apply(t, Array.prototype.slice.call(arguments));
            
            // 이때, 반환값이 객체이면 객체를, 아니라면 위의 객체를
            // 생성 객체로 지정합니다.
            INSTANCE = (typeof ret === 'object') ? ret : t;             
        }
        
        // 객체를 리턴합니다.
        return INSTANCE;
    }

    // 팩토리 메서드로도 접근할 수 있게 합니다
    c.getInstance = function() {
        return c.apply(null, Array.prototype.slice.call(arguments));
    }

    // 생성자를 대체한 클로저를 리턴
    return c;
};

// 테스트 함수
function javarouka(value) {
    this.v = value;
}

// 싱글톤화
var Single = Singletonify(javarouka);

// 테스트
var s1 = Single.getInstance("hello");
var s2 = new Single("javascript");
var s3 = new Single("world");

console.log(s1 === s2); // true
console.log(s2 === s3); // true

console.log(s1.v); // hello
console.log(s2.v); // hello
console.log(s3.v); // hello
위 코드의 포인트는 new를 사용한 객체 생성을 흉내내는 것과, 함수의 동작을 덮어 써버리는 것입니다.
좀 매끄럽지 않고 억지스러운 부분도 있는것 같습니다.
코드상 오류나 더 좋은 방법이 있다면 리플로 ...

참고자료

포스팅에는 아래 링크의 자료를 많이 참고했습니다.

작은 자유:  javascript singleton (자바스크립트 싱글턴)
http://ohgyun.com/248

Essential JavaScript Design Patterns For Beginners
http://addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript

Rhio.Kim's Blog: javascript singleton(자바스크립트 싱글 패턴) story
http://rhio.tistory.com/tag/%EC%8B%B1%EA%B8%80%ED%86%A4

2012. 2. 11.

CSS Object Model - clientWidth, offsetHeight, scrollLeft...

자바스크립트로 페이지의 요소를 컨트롤하려면 자주 보는 속성들이 있습니다

innerWidth, clientWidth, offsetHeight, scrollLeft ...

막상 처음 자바스크립트를 활용해서 페이지를 멋지게 꾸미려는사람을 혼돈의 카오스로 인도하는 속성들입니다.

뭐 이렇게 많은지...잘 정리해서 적재 적소에 써먹어 봅시다.

위에 나열한 속성들은 문서의 시각적 요소 (visual view) 에 대한 속성들로서, 사실 오래전부터 브라우저별로 구현되어 있었고 불문율같은 반표준 프로퍼티지만, 정식 표준은 아니며 지금 정규 표준이 제안되고 있다고 합니다.

CSS Object Model

W3C에서는 이렇게 명시하고 있습니다.

This is a draft document and may be updated, replaced or obsoleted by other documents at any time
(이것은 초안이며 갱신되거나 교체 혹은 언제든 무용지물이 될 수 있다.)

하지만 대다수의 브라우저가 지원하고 있고, 실제 쓰이고 있어서 크게 바뀔 일은 없을것 같습니다. http://www.w3.org/TR/cssom-view/에 가면 해당 설명을 확인할 수 있습니다.
영어에 자신이 있는 사람은 이 포스트를 읽기 보다 표준제안 문서의 정보를 참고하길 권합니다.

Window 관련

먼저 윈도우 요소 속성을 나타내는 인터페이스입니다.
(http://www.w3.org/TR/cssom-view/#extensions-to-the-window-interface)
partial interface Window {
    MediaQueryList matchMedia(DOMString media_query_list);
    readonly attribute Screen screen;

    // viewport
    readonly attribute long innerWidth;
    readonly attribute long innerHeight;

    // viewport scrolling
    readonly attribute long scrollX;
    readonly attribute long pageXOffset;
    readonly attribute long scrollY;
    readonly attribute long pageYOffset;
    void scroll(long x, long y);
    void scrollTo(long x, long y);
    void scrollBy(long x, long y);

    // client
    readonly attribute long screenX;
    readonly attribute long screenY;
    readonly attribute long outerWidth;
    readonly attribute long outerHeight;
};
innerHeight, innerWidth
브라우저의 뷰포트(viewport), 즉 사용자에게 보이는 영역을 나타냅니다. 하지만 특정 브라우저에서는 저 속성을 지원하지 않아 직접 구해야 합니다.
document 의 최상위 루트 엘리먼트(즉, html)의 넓이로 뷰포트를 구합니다.

다음과 같은 코드가 나오겠지요.
function getViewport() {      
    if(window.innerWidth !== undefined && window.innerHeight !== undefined) {
        return [ window.innerWidth, window.innerHeight ];
    }
    // 해당 속성이 없을 경우, document.documentElement
    // 혹은 document.body의 치수로 대체
    var doc = document.documentElement || document.body;  
    return [ doc.clientWidth, doc.clientHeight ];
}

지금 이 블로그를 보는 당신의 브라우저의 뷰포트 넓이,높이는 [0, 0]입니다.

scrollX(Y), pageX(Y)Offset
뷰포트가 실제 볼 수 있는 페이지에서 얼마나 이동했는지 알 수 있는 스크롤링 계열 속성들도 필요합니다.
드래그 앤 드롭 구현시에 화면 중앙 레이어 표시, 화면 강제 스크롤링등에 쓰이는 속성 및 함수들도 window 인터페이스에 정의되어 있습니다.
뷰포트가 얼마나 X축으로 스크롤 되었는지 알려주는 scrollX, pageYOffset 두개인데 이것들은 언제나 같습니다.
Y축의 scrollY, pageYOffset도 마찬가지입니다.
하지만 IE는 개성이 강해서 위 두 속성을 쓸 수 없고, 직접 계산으로 구해야 합니다.
function getPageOffset() {
    if(window.pageXOffset !== undefined  && window.pageYOffset !== undefined ) {
        return [ window.pageXOffset, window.pageYOffset ];
    }
    var doc = document.documentElement || document.body;
    return [ doc.scrollLeft, doc.scrollTop ];
}

지금 이 블로그를 보는 당신의 브라우저 뷰포트 스크롤 X,Y 은[0, 0] 입니다.

scroll, scrollTo, scrollBy
scroll(x, y), scrollTo(x, y): 같은 기능으로 윈도우를 해당 좌표로 스크롤 시키는 함수입니다.
scrollBy(x, y): 현재 스크롤에 인자값을 더한 위치로 스크롤시킵니다.



screenX, screenY (혹은 screenLeft, screenTop)
현재 스크린(사용자 기본 출력)에서 브라우저가 위치한 곳을 나타냅니다. IE, opera, safari에서는 screenX, screenY를 지원하지 않고 대신 screenLeft, screenTop를 지원합니다.

지금 이 블로그를 보는 당신의 브라우저의 스크린에서의 X,Y 좌표는 [0, 0]입니다.

outerWidth, outerHeight
브라우저 자체의 크기, 즉 상태바 주소입력창 메뉴 테두리...등을 모두 합한 크기를 리턴하는 속성입니다. 이번에도 IE는 outerWidth, outerHeight를 지원하지 않으므로, 직접 구해야 합니다.
offsetHeight라는 속성은 객체의 테두리(border)까지 합한 실제 크기를 가진 속성입니다.
위의 함수에서 사용한 방법처럼 문서 최상위 루트를 구한 뒤 offsetWidth, offsetHeight 속성으로 구할 수 있습니다.

지금 이 블로그를 보는 당신의 브라우저의 실제 크기는 [0, 0] 입니다.

Screen 관련

다음은 screen 객체의 인터페이스입니다.
(http://www.w3.org/TR/cssom-view/#the-screen-interface)
interface Screen {
      readonly attribute unsigned long availWidth;
      readonly attribute unsigned long availHeight;
      readonly attribute unsigned long width;
      readonly attribute unsigned long height;
      readonly attribute unsigned long colorDepth;
      readonly attribute unsigned long pixelDepth;
};
출력 장치. 즉, 모니터가 됩니다.

availWidth, availHeight : 윈도우 작업표시줄을 제외한 실제 스크린 크기
width, height : 실제 스크린 크기
colorDepth, pixelDepth : 스크린에서 색상, 해상도 수치를 반환.

0, 0, 0, 0

Element 관련

다음은 HTMLElement에 대한 것들입니다.
(http://www.w3.org/TR/cssom-view/#extensions-to-the-element-interface)

친숙한 프로퍼티가 많습니다.
이 포스트를 쓰게 된 원인중 하나인 clientLeft, offsetWidth 이런 것들을 가진 태그들의 속성이죠.

본래 W3C문서에는 Element와 HTMLElement를 구분짓고 있으나 편의상 합쳐 쓰겠습니다.
HTMLElement는 Element를 상속한 인터페이스지만, IE에서는 (IE8버전기준) HTMLElement를 완벽히 지원하고 있지 않습니다.
interface Element {
 
    ClientRectList getClientRects();
    ClientRect getBoundingClientRect();
 
    // scrolling
    void scrollIntoView(optional boolean top);
 
    attribute long scrollTop;   // scroll on setting
    attribute long scrollLeft;  // scroll on setting
 
    readonly attribute long scrollWidth;
    readonly attribute long scrollHeight;
 
    readonly attribute long clientTop;
    readonly attribute long clientLeft;
    readonly attribute long clientWidth;
    readonly attribute long clientHeight;
 
    readonly attribute Element offsetParent;
    readonly attribute long offsetTop;
    readonly attribute long offsetLeft;
    readonly attribute long offsetWidth;
    readonly attribute long offsetHeight;
};

getBoundingClientRect()
자신의 오프셋 정보를 객체형태로 반환합니다. 이때 계산은 뷰포트에 대한 상대 위치입니다.
top, bottom, left, right, width, height를 가지고 반환됩니다.

scrollIntoView()
스크롤을 이 메서드를 실행한 엘리먼트가 보일 때까지 스크롤 해 줍니다.

scrollWidth, scrollHeight
엘리먼트가 부모의 overflow 속성 지정 등에 가려져 실제 크기가 보이지 않더라도(스크롤바 등으로 감춰져 있더라도) 그 크기를 나타내 줍니다.

cilentTop, clientLeft
offsetTop 속성, 즉 기준이 되는 오프셋과 실제 박스의 left, top 수치의 차이, border의 픽셀값과 같습니다.

clientWidth, clientHeight
실제 객체의 크기를  나타냅니다.
크기 계산 시 padding은 포함하고, margin, border, 스크롤바는 제외합니다.

offsetTop, offsetLeft
offsetParent로 찾을 수 있는 객체에 대해 얼마나 떨어져 있는가 계산된 수치.
offsetParent는 부모의 position이 static이 아닌 속성일 때입니다.
부모중에 위치 지정 포지션(position: static이 아닌 것)이 없을 땐 offsetParent는 body를 기준으로 계산됩니다.

offsetWidth, offsetHeight
offsetParent로 찾을 수 있는 객체에 대해 상대적으로 크기가 계산된 수치입니다.
쉽게, 스크롤바와 border까지 포함한 개체의 크기라고 생각하시면 됩니다.


부록
아래 DIV 영역의 width, height는 200px 이며, border는 10px입니다.
안 이미지의 width, height는 300px 이고 margin, padding, border 는 0px 입니다.
버튼을 눌러보세요.

* 모바일, 동적 뷰 환경 등 특수한 환경에서는 이미지 크기가 자동조절되거나 스크롤바가 나오지 않아 원하는 대로 테스트가 되지 않을 수 있습니다.
















참고 이미지입니다.
출처는 인터넷에 떠도는 이미지를 가져왔습니다.


웹 사용자가 브라우저의 자바스크립트를 활성화하지 않았을 때

현대의 웹페이지는 자바스크립트를 거의 사용하고 있습니다.

심지어는 ActiveX나 플래시, Silverlight 등의 플러그인 조차도 자바스크립트의 도움을 받고 있는 현실입니다.

정석대로라면 우아한 낮춤(Gracefull degradation)을 사용하여 사용자의 접근성을 해치지 말아야 하지만, 스크립트가 멀쩡히 지원되는데도 어떠한 이유에서, 혹은 잘 모르고 자바스크립트가 동작하지 않아 사이트를 이용하지 못하는 사람들도 있을 수 있습니다.

만일 사이트가 자바스크립트의 도움이 절실하다면 <noscript> 태그를 사용하여 사용자에게 안내해주는 방법이 주로 쓰이는데 이 경우 안내 문구에 사용자에게 자바스크립트의 가동 방법까지 친절하게 안내해주는 페이지가 있어 소개합니다.


뭔가 심심하게 <noscript> 안에 자바스크립트를 켜주세요~ 이런 문구 하나 넣는 것보다 위 링크를 참조하게 하는 것도 괜찮을 것 같네요.

2012. 2. 7.

JavaScript의 Function의 속성 및 메서드 (수정)

# 개요 

자바스크립트에서 제일 중요한 요소는 함수입니다.
흔히 일급 객체 (first-class-object) 라고 불리는 자바스크립트 함수는 자바스크립트에서 거의 모든 것을 할 수 있습니다. 보통 언어에서 흔히 지원되는 함수의 동작인 실행, 값의 반환같은것은 당연하게 수행하며, 자신이 반환값이 되기도 하고 인자로서 넘겨지기도 하며, 객체의 프로퍼티나 변수 할당도 가능합니다.

심지어 변수 스코프 경계를 짓거나, new 연산자와 쓰여 다른 객체를 생성하는 생성자 역할 및 클로저로서도 동작하는 그야말로 만능 엔터테이너입니다.

그리고 많은 분들이 재미있어(?) 하시는 prototype 속성을 가진 객체이기도 합니다.

함수는 어떤 속성과 메서드를 갖는지 한번 알아봅시다.


함수의 속성들 

자바스크립트에서는 함수도 엄연한 Object이기에 속성을 가지고 프로그래머가 임의로 정의할 수도 있습니다. 또한 기본으로 여러 속성을 가지고 있기도 합니다.

그런 것들은 대부분 Function이라는 빌트인 함수의 프로토타입에서 상속된 것입니다.

arguments
함수가 실행될 때 바인딩되는 늦은 바인딩 변수입니다.
함수가 생성되었을 당시에는 null 값을 가지고 있습니다. 런타임이 굉장히 중요한 함수죠.

유사 배열 객체(array-like object)로서 함수가 실행되었을 때 전달되어 초기화된 인자를 순서대로 0번 인덱스부터 첨자로 하여 배열 형식으로 가지고 있습니다.
function javarouka() {
    // 인자를 따로 선언하지 않아도 런타임시 주어진 인자로 초기화됩니다.
    // 그리고 배열과 같은 기능을 하는 속성 length가 있습니다.
    for(var i=0; i < arguments.length; i++) {
        console.log(i + "째 인자의 값은 " + arguments[i]);
    }
    // false. 배열이 아닙니다.
    console.log (arguments instanceof Array)               
}
console.log(javarouka.arguments) // 실행 전에는 null 입니다.
javarouka("hello", "javascript", "function", "property!"); // 잘 출력되겠죠?
절대 배열로 착각하여 arguments.push() 나 arguments.slice() 같은 배열 메서드를 사용하지 마세요. 만일 사용하고 싶다면
Array.prototype.push.call(arguments, "밀어넣을 요소");
var aryArgs = Array.prototype.slice.call(arguments); // 진짜 인자 배열 얻기
형식으로 배열의 함수를 컨텍스트만 자신으로 바꿔서 사용하면 됩니다.
또한 실행중인 자기 자신을 참조하는 callee 속성도 눈여겨 볼만 합니다.
var i = 0;
(function() {
    if(i < 10) {
        document.writeln(i++);
        // arguments.callee은 자기 자신을 참조하는 속성입니다.
        setTimeout(arguments.callee, 100);
    }
})();
// 0123456789 가 출력될 겁니다
혹자는 이 arguments 속성이 자바스크립트에서 함수 호출의 유연함을 강조한다고 하지만 개인적으로는 혼돈에 빠뜨리는 속성이라고 생각합니다.
MDN에서는 이 속성을 Deprecated 로 정의하고 있지만, 기존 소스에서 사용되는 빈도가 높아 없어지진 않을 것 같습니다.

length
함수 생성시 지정한 받을 인자의 개수를 나타냅니다.
대다수의 브라우저에서 지원합니다. 하지만 경험상 막상 쓸일이 그리 없었던 듯 합니다.
function nonblock(val1, val2) {
    /* 뭔가 열심히 하는 로직 */
}
console.log(nonblock.length) // 2
주의할 점이 있는데, arguments.length의 값과는 다를 수 있습니다.
이쪽은 선언적인 인자의 숫자이며, arguments.length는 실행시 인자의 숫자입니다.

constructor
Object에서 상속받은 속성입니다. 자신을 생성한 함수를 가르킵니다.
일반적으로 콘솔이나 alert등으로 출력을 시도할 경우 네이티브 함수의 toString() 값인 function Function() { [native code] } 을 볼 수 있을 것입니다.
실제 반환되는건 Function 객체입니다.
function Niceguy() {
    /* ...이런저런 구현... */
}
// Niceguy 라는 함수객체는 네이티브코드에서 생성됩니다
console.log(Niceguy instanceof Function) // true.
console.log(Niceguy.constructor) // function Function() { [native code] }

// 반면, 생성자로 쓰일 경우 생성된 객체의 constructor가 됩니다.
var javarouka = new Niceguy();
console.log(javarouka.constructor) // Niceguy
caller
자신을 실행시킨 함수를 반환하는 비표준 속성입니다.
만일 속성값이 null이라면 글로벌 컨텍스트에서 실행한 것입니다.

다음 예제를 보세요.
var obj = {
    a: function() {
        return obj.a.caller;
    },
    b: function(f) {
        return f();
    }
}
obj.a(); // null
obj.b(obj.a); // obj.b 반환. toString()은 function (f) { return f(); } 가 됩니다.
name
함수의 이름을 반환하지만 이 경우 반드시 함수 선언문으로 한 경우에만 반환됩니다.
비표준이며, IE는 지원하지 않습니다.

(2012-02-17 추가)

함수 선언문 뿐 아니라 이름을 가진 함수 리터럴(named function-literal) 일 경우에도 name 속성이 나오는군요. 이때 함수가 할당된 변수가 name이 되는게 아닌 함수 리터럴에 지정한 이름이 name이 됩니다.
// 함수 선언문
function func1() { /* ... */ }
func1.name // "func1"

// 함수 표현식 변수할당
var func2 = function() { /* ... */ }
func2.name // "" ( or undefined or null? )

// 이름을 가진 함수 표현식
var func3 = function myFunc() { /* ... */ }
func3.name // "myFunc"

prototype
함수(내장함수도 물론 포함합니다)만이 가지고 있는 속성으로 자바스크립트 객체지향의 근간입니다. 이것만 왜 붉은 색이냐 하면 그만큼 중요하기 때문입니다.

설명은 기존의 포스트를 참고해 주세요
http://blog.javarouka.me/2011/12/prototype-property-and-prototype-chain.html


함수의 메서드들

함수도 객체이기에 메서드들을 가질 수 있습니다.
보통 함수에 사용자가 뭔가를 정의할일은 거의 없습니다. 라이브러리 레벨의 범용성을 추가할 때 사용되는게 대부분입니다.

아래 소개할 함수 대부분도 call이나 apply등을 빼면, 고급 기능에서나 사용될 법 한 것들입니다.

apply(컨텍스트 [, 인자의 배열 ])
call(컨텍스트 [, 인자1, 인자2....])
기능이 같으므로 함께 설명합니다.
이 메서드를 사용하면 함수를 실행시키는 컨텍스트를 직접 지정할 수 있습니다. 쉽게 말하면 this 변수에 연결될 객체를 선택할 수 있습니다.

apply, call 둘다 첫번째 인자는 활성 객체이며 두번째 인자부터만 다릅니다.
apply는 함수를 실행할 인자를 배열 형식으로 절달하며, call은 인자를 하나하나 나열하는 식이죠.
첫번째 컨텍스트를 전달하지 않거나 null을 주게 되면 글로벌 컨텍스트로 지정됩니다.

다음의 예제를 보세요.
function Computer(name, price) {    
    this.name = name;
    this.price = price;  
}
function Notebook(name, price) {
    // 호출 객체를 현재 생성될 객체로 지정합니다.
    Computer.apply(this, arguments);
    this.type = "portable";
}

var vaio = new Notebook("VAIO", 2250000);

vaio.name // "VAIO"
vaio.price // 2250000
vaio.type // "portable"
위의 Notebook 함수를 생성자로 써서 만든 객체 vaio는 생성자에서 Computer를 호출함으로써 name과 price를 자신의 속성으로 지정할 수 있지요.

보통은 저런 용도로 쓰진 않지만요...

bind(컨텍스트 [, 인자1, 인자2....])
위의 call, apply와 비슷하지만 이번에는 주체가 함수가 됩니다.
함수의 스코프 체인과 프로토타입을 첫번째 인자의 것으로 모두 바꾸고 나머지 인자를 함수에 전달하여 실행합니다.

말로 하니 복잡한데 예제를 봅시다.
var name = "Lee";
var other = {
    name: "Kim"
}
function getName() {
    return this.name;
}
getName() // 전역 참조. 결과는 "Lee"

var wasBound = getName.bind(other);
wasBound(); // 바인드 된 객체 참조. 결과는 "Kim"
함수를 특정 객체의 스코프 체인에 묶어버리는(bind) 기능입니다.
아직은 크롬과 파이어폭스에서만 지원됩니다.

toString
Object에서 상속한 메서드로서 본래 기능은 해당 값의 문자열 형식을 반환하게 되어 있습니다. 대부분의 자바스크립트 엔진 구현에서는 함수의 toString을 호출하면 함수 소스코드의 내용이 나옵니다. 네이티브 코드로 구현된 부분은 소스코드의 내용이 공개되지 않습니다.

문자열 결합 연산자 (+) 나 alert, document.write() 등의 인자가 되거나 해서 문자열이 필요한 경우 자동으로 호출되어 연산됩니다.
function javarouka() {
    /* 뭔가 한다 */
}
// toString을 override
javarouka.toString = function() {
    return "Happty Programer";
}
// 문맥상 문자열 연산이 필요할 때 자동으로 호출됩니다
console.log("블로그의 주인은 " + javarouka + "입니다");
valueOf
역시 Object에서 상속된 메서드로서 값의 원시값(primitive value) 값을 반환하게 되어 있습니다. 역시 대부분의 엔진에서는 함수 자체를 그대로 반환합니다.
-, /, * 등의 산술 연산에 함수를 직접 쓸 경우 자동으로 호출됩니다.

혹시라도 함수로 숫자 연산을 하고 싶다면 오버라이딩 해 두면 덧셈뺄셈등의 연산이 가능해 집니다. (그런데 그럴 일이 있을지 모르겠습니다)
function one(i18n) {
    if(i18n === "ko") return "하나";
    else if(i18n === "jp") return "いち";
    return "one";
}
one.valueOf = function () { return 1; }

// 원시 값 연산이 필요한 경우 자동으로 호출됩니다.
var result = 38 - one;
console.log(result) // 37
toSource
함수의 소스코드를 문자열로 반환합니다. 대부분의 경우 toString과 결과가 같습니다.