블로그 내 검색

2011. 12. 28.

Node.js에 대한 두 블로거의 다른 관점


Node.js은 암일까요?
Node.js에 대한 두 블로거의 상이한 시각. 재미있네요.
영어를 잘 못해서 번역기 돌렸지만요. 잘 읽으시는 분은 더 재미있을거 같군요.

ebay의 테크니컬 디렉터인 Ted Dziuba 라는 블로거가 먼저 "Node.js is Cancer" 라는 글로 비판하자, Brian Beck이 "Node.js cures Cancer" 라는 글로 반박했습니다.

Node.js is Cancer
(http://teddziuba.com/2011/10/node-js-is-cancer.html)

Node.js cures Cancer
(http://blog.brianbeck.com/post/10967024222/node-js-cures-cancer)

2011. 12. 21.

자바스크립트의 간단한 애니메이션 함수 구현해보기.

CSS3에서 새로 도입되는 새로운 프로퍼티인 transitiontransform 속성으로 브라우저의 애니메이션 효과는 이제 약간의 학습으로도 구현이 가능하게 되어가고 있다.
(transition의 예제는 신현석 님 블로그를 참고해보자. 잘 정리되어 있다.)
아직 브라우저 지원이 미흡하지만 요새 브라우저의 업데이트 경쟁을 보고 있으면 모든 브라우저에 CSS3이 도입되는건 멀지 않아 보인다.

다만 아직 구형 브라우저나, 기존 구축된 사이트에서는 HTML에 동적인효과를 주기 위해서 아직까지는 javascript가 많이 쓰이고 있는게 현실이고 그것을 유지보수할 때에도 관련 지식은 필요하다. 우리나라에서는 궁극의 최종보스 IE6도 아직 버티고 계시고 말이다.

애니메이션 구현에 대해서 조금 알아보자.

아래는 이번 예제에서 포스팅한 사용자 객체를 사용하여 구현해본 것이다.


나는 작업하면서, 그리고 이 글을 쓰면서 많은 JavaScript 애니메이션 코드를 보았다. JavaScript 애니메이션은 매끄럽지 못하다는 평가를 받는다. 모든 JavaScript 애니메이션은 window.setInterval 메소드를 사용하여 수행된다. 이것은 콜백의 간격과 콜백 함수를 지정하는 타이머 메소드이다. - Jack Herrington (http://www.ibm.com/developerworks/kr/library/x-ajaxslideshow/)


대부분의 javascript 애니메이션은 setTimeout 혹은 setInterval을 사용하여 구현된다.
브라우저에 들어가있는 클라이언트 자바스크립트가 스레드 하나로 구동되기 때문인데, 단일 스레드에서는 반복적인 작업(가령 루프문이라던가...)을 할 경우 다른 작업을 하지 못한다. 한마디로 화면이 굳어버린다는 뜻이다.

어쩔 수 없이 인터벌 류 함수를 사용해야 하는데, 이러한 함수들은 사용에 주의가 많이 필요한건 사실이다. 조심하지 않는다면 자칫 무한루프에 빠지거나, 메모리 릭이 발생한다.

이보다 더 심각한 문제는 시각적인 껄끄러움이다.

자바스크립트의 문제인지, 아니면 브라우저의 문제인지는 모르겠지만 인터벌 류의 함수를 사용하여 구현할 때 인자로 시간을 주게 되어 있는데 이 시간에 따라 애니메이션을 구현할 경우 조금 껄끄러운 움직임을 보여주게 된다.
아무리 시간을 적게 주어도 동작이 뭔가 어색해 보인다는 것인데, 이것은 인자로 주는 시간이 절대 그 시간에 실행하라는 명령이 아닌 제안이기 때문이다.
실제 20밀리초를 줘도 실행은 25초로 실행된다고 한다.

이것을 해결하기 위해서는 다른 방향으로 접근해봐야 한다.
시작시간을 기억해 뒀다가 경과한 시간만큼 진행 프로그레스를 구해서 그만큼을 움직이게 하는 것이다.

현재 애니메이션의 진행율을 검사해서 진행율만큼 속성을 변경시키는, 역으로 접근하는 방식이다.
자바스크립트의 Date 객체에서는 밀리초를 얻을 수 있는 valueOf() 메서드를 제공한다 . 이것으로 구현해 보자.

var javarouka = {

    UI: {
        // 계산된 스타일을 반환하지만, 
        // 스타일이 지정되어 있지 않을 경우, 브라우저별로 수치가 다르게 반환.
        // block 요소의 경우 넓이를 지정하지 않을 경우 
        // 계산된 수치를 반환하기도 하고, auto 값을 반환하는 경우도 있다.
        getStyle: function(ele, styleName) {

            // 스타일이 있다면 반환
            if(ele.style[ele]) {
                return ele.style[styleName];
            }

            // 인터넷 익스플로러 버전
            if(ele.currentStyle) {
                return ele.currentStyle[styleName];
            }

            // W3C 버전
            var st = document.defaultView && document.defaultView.getComputedStyle;
            if(st) {
                styleName = styleName.replace(/([A-Z])/g, "-$1").toLowerCase();
                var cs = document.defaultView.getComputedStyle(ele, "");
                return cs && cs.getPropertyValue(styleName);
            }

            // 별수 없다.
            else {
                return null;
            }
        },

        // CSS를 설정한다.
        css: function(ele, name, point) {
            ele.style[name] = point;
            return ele;
        },

        // top, left, width, height 관련 애니메이션을 처리한다.
        boundsAnimate: function(ele, css, duration, callback) {

            // 애니메이션 시작을 체크한다
            ele.$$isAnimate = true;
            
            // 시작시간 얻기
            var startTime = new Date().valueOf();

            var oldStyle = {}; // 구 스타일
            var newStyle = {}; // 조정 스타일
            var o = n = 0; // 임시 변수

            // 현재 스타일과 이동 스타일을 비교하여 조정스타일을 계산한다. 
            for(var k in css) {
                oldStyle[k] = parseInt(javarouka.UI.getStyle(ele, k), 10);
                if(!oldStyle[k] || oldStyle[k] == "auto") {
                    oldStyle[k] = 0;
                }
                n = parseInt(css[k], 10);
                newStyle[k] = n - oldStyle[k];
            }
            
            // 애니메이션 도중 오버플로우 요소를 노출하지 않는다.
            if(javarouka.UI.getStyle(ele, "overflow") !== "hidden") {
                javarouka.UI.css(ele, "overflow", "hidden");
            }

            // 실제 애니메이션 함수
            function inner() {

                // 지나간 시간 얻기
                if(ele.$$isAnimate) {
                    var passed = new Date().valueOf() - startTime;

                    // 지난 시간동안 진행해야 할 퍼센테이지 얻기
                    // 가령 애니메이션 시간이 최대 1000ms 이고, 
                    // 지난 시간이 100ms초라면
                    // CSS 변경 수치는 10퍼센트만큼 변경되어야 한다.                    
                    var    percent = passed / duration;
                    if (passed <= duration) {
                        for(var k in newStyle) {
                            var changed = 
                               oldStyle[k] + 
                               parseInt(newStyle[k], 10)
                                * percent;
                            javarouka.UI.css(ele, k, changed + "px");
                        }
                        setTimeout(inner, 0);
                    }
                    else {
                        // 종료시간이므로 목표로 했던 스타일을 그냥 적용
                        for(var k in css) {
                            javarouka.UI.css(ele, k, css[k]);
                        }
                        //콜백이 있다면 실행
                        if(typeof callback === 'function') {
                            callback(ele, css);
                        }
                    }
                }
            }
        },

        // 멈춤을 체크한다.
        stop: function(ele, isBack) {
            ele.$$isAnimate = false;
        }
    }
};

이런 식으로 구현하게 되면 브라우저의 딜레이나 랙으로 인해 연산이 느려져도 비교적 정확한 시간에 애니메이션이 종료되며, 종료될 때 목표했던 스타일이 지정되고, 무엇보다 밀리초를 직접 지정하는 방식보다 부드럽게 동작한다.

코드는 주석을 자세하게 첨부하였으니 어렵지는 않을 것이다. 아래는 위의 예제를 사용하는 코드이다.
(포스트 상단에 있는 예제를 동작시킨 코드)
var javaroukaSmaller = function(ele) {
    javarouka.UI.boundsAnimate(
        ele, 
        {
            "left": "50px",
            "width": "150px",
            "height": "150px"
        },
        1000,
        function(ele, css) {
            javaroukaLargger(ele);
        }
    );
};
var javaroukaLargger = function(ele) {
    javarouka.UI.boundsAnimate(
        ele, 
        {
            "left": "0px",
            "width": "300px",
            "height": "200px"
        },
        1000,
        function(ele, css) {
            //javaroukaSmaller(ele);
        }
    );
};
var javaroukaStop = function() {
    javarouka.UI.stop(document.getElementById("box"));
};

var javaroukaStart = function() {
    javaroukaSmaller(document.getElementById("box"));
}
... 그런데 jQuery있으면 다 필요없는데...?

2011. 12. 16.

유용하지만 잘 알려지진 않은 자바스크립트 속성


바로 본론으로...

event.button
button이라는 속성은 이벤트 객체(모든 이벤트 리스너에 첫번쨰 인자가 되는 객체)의 프로퍼티이다.
여기서 버튼이란 것은 마우스의 어떤 버튼을 클릭했는가에 따라 다른 값을 가진다.
하지만 역시 좀 개성있는 Internet Explorer에서는 버튼의 값이 일반적 W3C 브라우저와 다르다

아래는 그 테이블이다.

 구분  W3C  Internet Explorer
 왼쪽 버튼 클릭  0  1
 가운데 버튼 클릭  1  4
 오른쪽 버튼 클릭  2  2

만일 이것을 실제 적용하려면 표준과 비표준을 구분하던가, 오른쪽 마우스 버튼만을 캐치하는 방법이 좋겠다.(오른쪽은 표준 비표준 둘다 같다.)
더 많은 프로퍼티가 있지만, 주로 쓰이는 것은 저 셋 값일 것이다.



event.relatedTarget/event.fromElement
이 속성들도 역시 이벤트 객체의 속성으로, 마우스의 추적에 관련된 속성이다.
W3C가 relatedTarget이고 Internet Explorer가 fromElement이다.
이 속성에는 마우스가 한 엘리먼트에서 다른 엘리먼트로 옮겨갔을 때, 전에 있던 엘리먼트의 레퍼런스가 지정된다.
잘 사용하면 엘리먼트의 순서 관련 이벤트나, 드래그 앤 드롭시 제약같은 걸 쉽게 컨트롤할 수 있을것이다.



event.currentElement/event.toElement
방금전 속성과 대비되는 속성이다.
W3C가 currentElement이고 Internet Explorer가 toElement이다.
이 속성에는 마우스가 한 엘리먼트에서 다른 엘리먼트로 옮겨갔을 때, 현재 있는 엘리먼트의 레퍼런스가 지정된다.



window.beforeunload
윈도우 객체의 이벤트 리스너중 하나이다.
이 이벤트는 리스너가 문자열을 반환할 경우, 창이 어떠한 이유(새로고침, 닫기, 뒤로가기 등등)에서건 언로드되기 전에 핸들러가 호출되며, 승인할 경우에만 창이 언로드된다. 반환 문자열은 안내 문구로 반환 문자열이 표시되지만, FireFox는 문자열 출력을 지원하지 않고 단순히 페이지에서 나갈지 여부만을 묻는 모질라 자체에서 정의한 내장 문자열이 출력된다.

생소하게 느껴질거 같아 코드를 첨부한다.
window.onbeforeunload = function (e) {
    // 작업이 저장되지 않았을 경우에는 안내 메시지와 확인 창을 보여준다.
    if(!isJobSave) {
        return '페이지를 벗어날 경우 작업 내용이 저장되지 않습니다. 나가시겠습니까?';
    }
};


element.select
입력가능한 폼 엘레먼트(input, textarea 등)에 입력된 문자열을 마우스로 끌어 선택했을 때 발생한다.
Internet Explorer와 W3C가 이벤트 발생 시점이 다른데, Internet Explorer는 새로운 문자열이 선택될 때마다 리스너가 호출되며 표준 브라우저는 선택을 시작한 뒤 마우스 버튼을 떼면 리스너가 실행된다.

필드 안의 텍스트의 선택을 금지한다던가 하는 용도로 쓸 수 있지만, 그럴 일이 있을까?




element.innerText
innerHTML은 익히 알겠지만 innerText는 잘 모를 수도 있을 것이다.
말 그대로 엘리먼트 안의 텍스트만을 반환한다.
애석하지만 Firefox는 지원하지 않아 따로 구현을 해야 한다.

다음처럼 구현할 수 있다.

function getText(element) {
    var ret = '';
    
    // innerText 프로퍼티가 있다면 그냥 그 프로퍼티값을 리턴
    if(element.innerText) {
        ret += element.innerText;
    }
    else {
        // 그렇지 않다면 텍스트노드의 nodeValue 값을 재귀호출로 더해가며 출력
        element = element.childNodes || element;
        for(var i=0; i < element.length; i++) {
            if(element[i].nodeType !== 1) {
                ret += element[i].nodeValue;
            }
            else {
                ret += getText(element[i]);
            }
        }
    }
    return ret;
}



element.cloneNode(true|false)
엘리먼트를 완전 복제한다.
인자로 불린값을 받는데, true일 경우 하위 노드까지 복제하고 false일 경우엔 자신만을 복제한다.

프로 자바스크립트 테크닉

프로 자바스크립트 테크닉 (원제 Pro JavaScript Techniques) 은 jQuery로 유명한 john resig이 쓴 책이다.

자바스크립트의 실전 활용에 대해 적극적으로 다루고 있다.

책 초반부에는 DOM API에 대해 유용하고 멋진 예제들을 다수 제공하며, 소스코드의 설명까지 비교적 자세히 해주는 편이다.

그러나 문제는 책 후반부.

저자라도 바뀐 것인지 책의 퀄리티가 급격히 떨어진다.
기존 함수의 재탕으로 학원에서나 만들 법한 내용들로 채워져 있다.

초반부에서 무릎까지 치면서 시간가는줄 모르고 설명과 코드를 읽다가, 갑자기 후반부에 몇글자도 집중해서 읽지 못하고 결국 책을 덮게 만들어 버린다.

특히 이미지슬라이드 관련, 워드프레스 블로그 관련 등등의 예제에서는 인내력 테스트와 같은 지루함을 느꼈다.

코드들도 소소한 오타가 있어 그대로 C&P로 썼다간 돌아가지 않는다.
예제를 돌려볼 거라면 반드시 코드를 다시한번 점검해봐야 한다.
(코드 리딩 능력과 복습을 시키는 저자의 배려?)

하지만 책 초반부의 테크닉 설명과, 후반의 부록부분은 굉장히 도움이 되는 것들이다.
전체적 구성으로 판단하면 구매후 읽어도 후회는 없는 책이라고 생각한다.

2011. 12. 15.

간단한 자바스크립트 텍스트 검색 샘플

아이디 매니저 이름
아이디 매니저 이름 값1 값2 값3
id-1 name-0 column-1 column-1 column-1
id-2 name-0 column-2 column-2 column-2
id-3 name-0 column-3 column-3 column-3
id-4 name-0 column-4 column-4 column-4
id-5 name-1 column-5 column-5 column-5
id-6 name-1 column-6 column-6 column-6
id-7 name-1 column-7 column-7 column-7
id-8 name-1 column-8 column-8 column-8
id-9 name-1 column-9 column-9 column-9
id-10 name-2 column-10 column-10 column-10
id-11 name-2 column-11 column-11 column-11
id-12 name-2 column-12 column-12 column-12
id-13 name-2 column-13 column-13 column-13
id-14 name-2 column-14 column-14 column-14
id-15 name-3 column-15 column-15 column-15
id-16 name-3 column-16 column-16 column-16
id-17 name-3 column-17 column-17 column-17
id-18 name-3 column-18 column-18 column-18
id-19 name-3 column-19 column-19 column-19
id-20 name-4 column-20 column-20 column-20

2011. 12. 14.

블로그 스크립트 테스트 - 간단한 스크립트 애니메이션


간단한 애니메이션 이벤트 처리

click content


/*
<div class="top_wrapper">
    <div class="menu_area" >
        <div >Item D1</div>
        <div >Item D2</div>
        <div >Item D3</div>
        <div >Item D4</div>
    </div>
    <div class="menu_area" >
        <div >Item D1</div>
        <div >Item D2</div>
        <div >Item D3</div>
        <div >Item D4</div>
    </div>

    ...

</div>
*/
// 먼저 스크립트 https://www.google.com/jsapi를 임포트해야 한다.
var javarouka = function() {
    
    google.load("jquery", "1.7.1");
    google.setOnLoadCallback(applyPostMouseEvent);

    function applyPostMouseEvent() {
        var wrapper = $("div.top_wrapper");   
        var toBind = wrapper.find("div.menu_area div");

        function moveToSibling(e) {
            var target = $(e.target);
            if(!target.is("div")) {
                return;
            }
            var targetParent = target.parent();
            var moveTo;
            if(targetParent.is("div.menu_area")) {
                moveTo = targetParent.next("div");
                if(!moveTo.size()) {
                    moveTo = wrapper.find("> div:first-child");
                }

                // 임시 엘리먼트를 이동할 위치에 추가하여 실제 이동될 오프셋과 실제 크기를 구한다.
                var tmpDiv = 
                    $("<div>")
                    .css({
                        "visibility": "hidden",
                        "width": "60px"
                    })
                    .text(target.text());
            
                moveTo.append(tmpDiv);
                var t = tmpDiv.offset().top
                    l = tmpDiv.offset().left,
                    w = tmpDiv.width(),
                    h = tmpDiv.height

                // 구하고 나면 임시 엘리먼트를 삭제한다.
                tmpDiv.remove();
            
                target
                .css({
                    "position": "absolute",
                    "width": w,
                    "height": h,
                    "top": target.offset().top,
                    "left": target.offset().left
                }).appendTo($(document.body))
                .animate({
                    "top": t,
                    "left": l
                    },
                    function(e) {
                        moveTo.append(target.css({
                            "position": "static",
                            "top": "",
                            "left": ""
                        }));
                    }
                );
            }
        }
        $.each(toBind, function(index, element) {
            $(element).bind("click", moveToSibling);
        });
    }
}

2011. 12. 13.

prototype property and prototype chain (수정)

# 글을 쓴지 좀 됐는데 방문자가 아직 많네요 ~ 좋습니다 :)
# 곧 글을 대폭 수정할 예정입니다; 오래전에 쓴 글이라 잘못된게 많네요

prototype

1 명사

개요


자바스크립트의 모든 객체에는 자신이 생성될 당시의 원본 객체에 대해 숨겨진 연결을 갖는다.
그 연결의 대상이 되는 객체를 prototype이라고 한다.
자바스크립트의 모든 객체는 거슬러 올라가 보면 Object를 원본 객체로 하여 생성되어 있다.

이 말은 다음과 같이 해석할 수도 있다.
"자바스크립트의 모든 객체는 Object의 prototype에 모두 연결되어 있다."

객체지향적으로 말하면,
"자바스크립트의 모든 객체는 Object를 상속한다" 라고도 할 수 있다.

사실 클래스 기반의 객체지향 언어를 공부한 사람이라면 이미 어리둥절 할 것이다. 객체에서 객체를 만든다니.
이렇게 객체에서 객체를 만드는, 객체를 원형(prototype)으로 하여 객체의 복제 방법으로 새로운 객체를 만들어 내는 방식을 프로토타입 기반 프로그래밍이라고 부른다.


Object-Oriented

객체지향(Object-Oriented)을 하면 보통 프로그래머들은 클래스, 인스턴스, 인터페이스등을 떠올릴 것이다. 이런 개념들로 프로그래밍하는 것을 클래스 기반 객체지향 프로그래밍이라고 한다.
자주 쓰이는 C++, Java, C# 등의 많은 유명한 언어들이 이러한 방식을 가지고 있다.

그러나 객체지향 프로그래밍이 모두 클래스를 쓰는 것은 아니다.

프로토타입 기반 객체지향 프로그래밍이라는 개념도 있다. 이 방식은 객체지향의 개념을 다른 방식으로 프로그래밍한다. 객체의 원형인 프로토타입이 있고 그것을 사용하여 새로운 객체를 만든다. 새 객체를 만들었다면 그것을 바로 쓸 수도 있고 다시 그 객체에 무언가를 더 추가하여 그 객체를 사용하여 또 다른 새로운 객체를 만들어낸다.
Rua, Ecmascript(javscript, actionscript 등을 포함)하는 언어가 이러한 방식이다.


prototype property

자바스크립트에서는 클래스가 없고(class-less) 프로토타입 지향(prototype-oriented) 방식의 프로그래밍이라고 설명했다.

이것을 나타내는게 prototype 프로퍼티이다.
모든 '함수' 객체는 prototype 이라는 프로퍼티를 가진다.

코드로 prototype을 하나하나 살펴보자

var IamFunc = function() {
    var a = 1;
    function inner() {}
}
// 모든 함수는 prototype 프로퍼티를 가지고 있다.
typeof IamFunc.prototype // object

// 객체는 prototype 프로퍼티를 가지고 있지 않지만,
// prototype에 숨겨진 연결이 있어 참조만은 가능하다.
// 일부 엔진 구현에서는 __proto__ 속성으로 객체도 prototype 속성에 접근할 수 있다
var aObject = new IamFunc();

// 객체 리터럴은 생성자가 내장 함수인 Object를 통해 만들어진 객체 표현식이다.
var obj = {
    name: "object",
    method: function() {
        return "method";
    }
}

typeof IamFunc.prototype; // object;
typeof aObject.prototype; // undefined;
typeof aObject.constructor.prototype; // object; == IamFunc.prototype
typeof obj.prototype; // undefined
코드를 보면 알겠지만, 객체에는 prototype 프로퍼티가 없다. 오로지 함수 객체에만 존재한다.
그리고 prototype 객체 또한 객체이기에 javascript Object의 기본 속성을 그대로 가지고 있고, 추가적으로 constructor라는 자신을 참조하는 속성을 가지고 있다.
constructor는 자기 자신인 함수를 참조하는 속성이고 함수는 prototype 객체의 프로퍼티로 constructor를 가지고 있으니 순환 참조(?) 관계다.

// 앞으로 생성할 객체의 프로토타입이 될 객체를 만든다.
var carProto = {
    name: "object",
    model: "basic",
    madeby: "unknown",
    color: "free",
    getModel: function() {
        return this.model;
    },
    toString: function() {
        return "this is the " + 
            this.name + ", model is " + 
            this.model + " year. color is " + 
            this.color;
    }
}

// Grandure 객체 생성자를 만든다.
var Grandure = function() {
    this.model = "2011";
    this.name = "grandure";
    this.madeby = "hyundai";
    this.color = "black";
}
// 프로토타입 프로퍼티에 carProto를 지정한다.
Grandure.prototype = carProto;

// Tico 객체 생성자를 만든다.
var Tico = function() {
    this.model = "1996";
    this.name = "tico";
    this.madeby = "daewoo";
    this.color = "white";
}
// 프로토타입 프로퍼티에 carProto를 지정한다.
Tico.prototype = carProto;

// 객체 생성 후 toString 호출.
var myCar = new Grandure();
var yourCar = new Tico();
myCar.toString(); // "this is the grandure, model is 2011 year. color is black"
yourCar.toString(); // "this is the tico, model is 1996 year. color is white"
위 코드에서는 prototype 객체를 기반으로 새로운 객체를 생성하고 있다. 
prototype 객체의 모든 프로퍼티 및 메서드를 이어받아 새로운 객체가 만들어진다.
// 원형 프로토타입을 수정한다.
carProto.toString = function() {
    return "this is the " + this.name + ", made in " + this.madeby;
}

// 두 객체에 변화된 toString이 즉각적으로 적용된다.
myCar.toString(); // "this is the grandure. made in hyundai"
yourCar.toString(); // "this is the tico. made in daewoo"
만일 어떤 함수의 prototype 속성을 변경하면, 그 함수의 prototype을 기반으로 해서 생성된 모든 객체에 즉각적으로 그 변화된 사항이 적용된다. 주의할 점은 이미 생성된 객체에서는 prototype에서 상속된 객체를 덮어쓸 수 없다.(read-only) 객체에는 prototype 프로퍼티가 없기 때문이다. 속성을 덮어쓰는 시도를 하더라도 객체 자체의 속성이 새로 지정될 뿐, prototype에는 변화가 없다.
// toString을 수정한다.
yourCar.toString = function() {
    return "this car is very small";
}

// 변경되었다
yourCar.toString(); // "this car is very small"

// 원본은 변경되지 않는다.
carProto.toString(); // "this is the object, made in unknown"

prototype-chain (prototype-hidden-link)

객체는 prototype에 숨겨진 연결을 갖는다고 했다. 어떠한 방식일까.
객체에서 
prototype을 참조하는 방식은 다음과 같다. 코드로 풀어보자.

내장 객체 Object에 함수 swim을 추가한다.

var swimmable = new Object();
swimmable.swim = function(){ 
    return "swimming now";
}
Duck이라는 이름의 함수를 선언하고 위에 선언한 내장 객체를 프로토타입으로 지정한다.
이 객체에 name이라는 속성과 getName이라는 함수를 추가한다.
그리고 Duck을 생성자로 새로운 객체를 만든다. 이 aDuck이라고 이름짓자.

var Duck = function(){}
Duck.prototype = swimmable;
Duck.prototype.comment = "I can Swim!";
Duck.prototype.getComment = function() {
    return this.comment;
}
var aDuck = new Duck();
 이제 다시 Goose라는 함수를 선언한 뒤, 위에서 생성된 aDuck을 프로토타입으로 지정하고, age라는 속성과, getAge라는 함수를 추가한 뒤 Goose를 생성자로 새로운 객체를 생성한다. 이 객체는 aGoose라고 붙인다.

var Goose = function() {
    this.age = 5;
}
Goose.prototype = aDuck;
Goose.prototype.getAge = function() {
    return this.age;
}
var aGoose = new Goose();
이제 Penguin 함수를 만들고, 위에서 생성된 aGoose을 프로토타입으로 지정한다.
그리고 객체를 만들어 aPenguin 이라고 한다. 그 뒤 aPenguin에 getHometown이라는 메서드를 추가한다.

var Penguin = function(home) {
    this.hometown = home;
}
Penguin.prototype = aGoose;
Penguin.prototype.getHometown = function() {
    return this.hometown;
}
var aPenguin = new Penguin("Antarctica");
훌륭한(?) 오리같은(?) 펭귄 객체가 만들어졌다.
이 펭귄의 메서드는 주어진게 없다.

aPenguin.swim(); // swimming now
aPenguin.getComment(); // I can Swim!
aPenguin.getAge(); // 5
aPenguin.getHometown(); // Antarctica
하지만 헤엄(swin)도 칠줄 알고 나이가 몇인지(getName), 어디 사는지(getHometown)도 알 수 있다.
만일 swim을 호출했을때 언어 처리기에서 어떻게 처리하는지 보자.

swim 함수가 호출될 경우 aPenguin에는 그러한 
함수가 없으므로 자신의 생성자의 prototype을 참조(prototype-chain)한다. 그러나 Penguin의 prototype에도 swim은 없다. 그렇다면 부모였던 Goose의 프로토타입을 참조해보지만 역시 없다 그렇다면 Duck의 프로토타입을 참조한다. Duck의 프로토타입은 swimmable라는 객체로 swim 함수를 가지고 있다.
그렇기에 성공적으로 swim이 실행되어 펭귄이 헤엄을 칠수 있게 된다.

여기서 재미있는 점이 있다.
펭귄(penguin)은 오리가 아니다. 거위(Goose)는 오리과가 맞지만 말이다.
하지만 자바스크립트 언어에서는 어떠한 객체가 오리처럼 행동한다면 그것은 오리라고 가정할 수 있다. (
Duck-typing)


사족


사실 클래스 기반의 언어에 익숙한 사람은 이러한 뭔가 이질적인 문법과 개념에 익숙치 않을 수 있다.
하지만 이러한 방식이 있다는 것을 이해하고 익숙해진다면 색다른 프로그래밍에 흥미를 느낄거라고 생각한다.

사족이지만, 이러한 프로토타입의 방식에 의문을 품거나, 비효율적이라고 생각한 ECMA 인터내셔널의 표준 제안자들은 자바스크립트 계열 언어의 4번째 개편안에서 클래스 기반의 특성을 다수 첨가하려다가 많은(특히 프로그래머) 사람들에게 반발을 샀으며 뜨거운 논쟁까지 불러일으켰다고 한다. 결국 4번째 개편안은 버려지게 되었고 지금 한창 5가 제안중이다. (이미 나왔나;)

특히 더글라스 크록포드는 이 상황에서


"현업 개발자가 아닌 언어 디자이너와 비평가들은 언어를 개선하려는 시도조차 하지 말라"

라고 했다고.

2011. 12. 7.

HTML 이벤트 버블링(Event Bubbling) 에 대해서

자바스크립트의 이벤트 처리는 아주 직관적이다.
그 만큼 사용이 간단한 지식만으로 쉽게 사용이 가능하다.

그런데 간혹 이벤트의 발생이 원하지 않는 방향으로 흐를 때가 있다.
특히, 마우스 이벤트 처리시에 자주 발생되는데, 메뉴 드랍다운 UI를 자바스크립트로 구현하면서 많이 겪어봤을 거라고 생각한다.
드랍메뉴 영역에 mouseout을 걸어서 영역을 벗어날 때 사라지게 했는데, 결과는 메뉴 영역의 아이템에 마우스를 갖다대도 사라져 버린다든가...

골치아프다. 이벤트가 왜 이러는거야!

이벤트 버블링에 대해 예제와 함께 좀더 알아보자.
아래와 같은 예제를 준비했다.

div 를 여러개 계층 구조로 겹쳐 두었다.

<!DOCTYPE html>
<html>
<head>
<style>
div { margin: 10px; padding: 10px; background-color: red; }
div div { background-color: yellow;
div div div { background-color: blue; }
textarea { width: 90%; height: 200px; }
</style>
</head>
<body>

<div id="depth1">
<div id="depth2">
<div id="depth3">
</div>
</div>
</div>
<textarea></textarea>
</body>
</html> 

그다음 이벤트 바인딩이다.
div영역에 마우스를 올릴 때, 해당아이디를 텍스트에리어에 출력한다.

window.onload = function(e) {

    var logger = document.getElementsByTagName("textarea")[0];
    function log(newtext) {
        logger.value += newtext + "\n";
        logger.scrollTop = logger.scrollHeight; 
    }
    var divs = document.getElementsByTagName("div");
    for(var i=0; i < divs.length; i++) {
        (function(){
            var div = divs[i];
            div.onmouseover = function(e) {
                if(div.id === "depth1") {
                    log(div.id);
                }
                else if(div.id === "depth2")  {                    
                    log(div.id);
                }
                else if(div.id === "depth3") {
                    log(div.id);
                }
            }
        })();
    }
}


가장 안쪽의 depth3에 마우스를 올려보자.
실행 결과를 보면 아래 영역의 아이디부터 textarea에 출력되는것을 볼 수 있다.

depth1
depth2
depth1
depth3
depth2
depth1

JS Bin on jsbin.com
이제 어떤 일이 일어났는지 확인해보자.
먼저 앞으로 쓰일 용어 정의부터 하자

이벤트 핸들러 : 요소에 어떠한 이벤트가 일어날 때 실행되는 함수. 캡처 핸들러와 버블 핸들러로 구분할 수 있다.
요소 : HTML Element

HTML 이벤트 모델에서 이벤트가 실행되는 것은

캡처 (Capture)
버블 (Bubble)

이라는 두 단계가 있다.

캡처는 말 그대로 캡처이다.
이벤트가 뭔가에 의해 발생하였다면 그 이벤트를 캡처하기 위해 이벤트가 발생한 요소를 포함하는 부모 HTML부터 이벤트의 근원지인 자식 요소까지 이벤트를 검사한다. 이때, 캡처 속성의 이벤트 핸들러가 있다면 실행시키면서 이벤트 요소로 접근한다.

이렇게 이벤트의 근원을 아래로 내려가며 찾아가는 단계를 이벤트 캡처링(Event Capturing)이라고 부른다.

이제 캡처가 끝났으니 버블이 발생한다.

이벤트 요소에 도달했다면 이제 다시 이벤트 요소로부터 이벤트 요소를 포함하고 있는 부모 요소까지 올라가며 이벤트를 검사한다.
이때 버블 속성의 이벤트 핸들러가 있다면 실행시킨다.

마우스를 영역에 갖다대면 이벤트는 하나에서만 발생하는것이 아니라 모든 영역에서 발생한다. 가장 안쪽의 div에 마우스를 갖다대면 이벤트의 발생 순서는 안쪽 div부터 바깥쪽의 부모 div로 전파되며 발생한다.

여기서는 모든 div에 이벤트 핸들러를 할당했지만, 이벤트 핸들러가 있든 없든 상관없이 이벤트 처리기는 이벤트를 체크하며 핸들러가 있을 경우 실행시키면서 차례로 상위 요소로 이벤트를 전파시킨다.

사이다등의 탄산 음료를 컵에 담아두면 탄산 기포가 아래에서 위로 올라오는 것을 봤을 것이다. 그것과 같이 이벤트도 자식 요소로부터 부모 요소로 올라오며 실행된다고 하여 이벤트 버블링(Event Bubbling)이라고 부른다.

보통 기본 이벤트 핸들러는 버블 속성이며 W3C 표준에서는 이벤트를 묶을 때 캡처 핸들러인지 버블 핸들러인지 지정할 수 있게 되어 있다.
하지만 인터넷 익스플로러 계열은 캡처 이벤트를 지원하지 않는다.

위의 예제에서도 모든 이벤트 핸들러는 버블 이벤트 핸들러이다.

그럼 위의 예제의 결과를 이해할 수 있을것이다.
  • depth1에 진입하여 아이디를 출력
  • depth2에 진입하여 아이디를 출력하고 이벤트는 버블되어 depth1의 아이디를 출력
  • depth3에 진입하여 아이디를 출력하고 이벤트는 버블되어 depth2, 또 버블되어 depth1 의 아이디를 순서대로 출력
이제 한번 캡처링 이벤트 핸들러를 등록하고 실행 순서가 어떻게 되는지 알아보자.
주의할 점은 아래 예제는 인터넷 익스플로러에서는 동작하지 않는다.

캡처 이벤트를 지원하려면 다음 표준 메서드가 필요하다.


element.addEventListener
target.addEventListener(이벤트타입, 핸들러, 캡처여부);

위의 예제에서 이벤트를 등록하는 부분을 아래와 같이 변경한다.

var divs = document.getElementsByTagName("div");
if(document.addEventListener) {
    for(var i=0; i < divs.length; i++) {
        (function(){
            var div = divs[i];
            if(div.id === "depth1") {
                div.addEventListener(
                    "mouseover", 
                    function(evt) {
                        log(div.id);
                    },
                    true // 이벤트 캡처로 등록
                )
            }
            else if(div.id === "depth2") {
                div.addEventListener(
                    "mouseover", 
                    function(evt) {
                        log(div.id);
                    },
                    false // 이벤트 버블로 등록
                )
            }
            else if(div.id === "depth3") {
                div.addEventListener(
                    "mouseover", 
                    function(evt) {
                        log(div.id);
                    },
                    false // 이벤트 버블로 등록
                )
            }
        })();
    }
}
코드가 복잡해 보이지만 사실 단순한 반복 코드이다.


실행 순서를 예측한 사람이라면 캡처와 버블에 대해 정확하게 이해하고 있는 것이다.
실행 순서는 다음과 같다

depth1
depth1
depth2
depth1
depth3
depth2
  • depth1에 캡처하면서 캡처 이벤트 핸들러 동작, 아이디를 출력
  • depth2에 캡처하면서 depth1 아이디를 출력하고 이벤트는 버블되며 depth2의 아이디를 출력
  • depth3에 캡처하면서 depth1 아이디를 출력하고 이벤트는 버블되며 depth3, depth2의 아이디를 순서대로 출력
JS Bin on jsbin.com 이러한 이벤트 동작은 때로 원치 않는 결과를 가져온다.
그러나 이벤트 버블 동작을 막는 방법도 지원한다.
물론 인터넷 익스플로러는 이번에도 따로 논다. 익스폴로러를 죽입시다 익스플로러는 나의 원수

eventObject.stopPropagation() // W3C방식
eventObject.cancelBubble = true; // 인터넷 익스플로러 방식


이벤트 핸들러에서 이벤트 객체의 특정 메서드를 샐행하거나 속성을 수정하면 버블링을 막을 수 있다.

우리가 원하는 이벤트 버블 막기 작업을 위해서는 먼저 이벤트 객가 필요하다. 이벤트 객체는 어디 있을까?

W3C 표준에 따르면 이벤트핸들러에는 첫번째 인자로 이벤트 객체가 전달된다.
그러나 인터넷 익스플로러는 전역 객체의 속성에 이벤트 객체가 바인딩된다.

이벤트 객체를 확인하는 간단한 함수 예제이다.

function alertEventObject(e) {
    var objEvent = e || window.event;
    alert(objEvent + " is Event Object");
}


첫번째 예제의 depth2 이벤트 핸들러 지정 부분을 아래와 같이 수정한다.

else if(div.id === "depth2") {
    var evt = e || window.event;
    if(evt.stopPropagation) {
        evt.stopPropagation();  // W3C 표준
    }
    else { 
        evt.cancelBubble = true; // 인터넷 익스플로러 방식
    }
    log(div.id);
}

그렇다면 실행 결과는 다음과 같이 될 것이다.

depth1
depth2
depth3
depth2
  • depth1에 진입, 아이디를 출력
  • depth2에 진입, depth2의 아이디를 출력하고 이벤트를 버블시키려 하지만 이베트 버블이 막아져 있어 이벤트 전파 종료
  • depth3에 진입, depth3 아이디를 출력하고 이벤트는 버블되어 depth2 의 아이디를 출력, 그뒤 버블이 막혀 이벤트 전파 종료.

이벤트 버블링을 정확하게 이해하고 있어야 복잡한 자바스크립트 어플리케이션을 구현시에 알 수 없는 오류를 줄일 수 있을 것이다.

2011. 12. 2.

프로 자바스크립트 테크닉 책 오타 코드의 Closure

요새 프로 자바스크립트 테크닉(원제 : pro javascript techniques)이라는 책을 읽고 있다.

jQuery 로 알려진 자바스크립트 행복전도사(?) john resig 이 쓴 책으로, 서두부터 기초따위 집어치우고 자바스크립트의 여러 요소 및 테크닉을 다루고 있다.

그런데 코드 오류가 몇몇 보인다.
특히 사람들이 자바스크립트에서 까다로워하는 Closure 에서 말이다;

책에 실린 33페이지의  Closure 의 오류를 설명하는 코드중 일부이다
 
var obj = document.getElementById("main");
var items = [ "click", "keypress" ];
for(var i = 0; i < items.length; i++) {
    (function() {
        obj[ "on" + items[i] ] = function() {
            alert( "Thanks for your " + items[i] );
        };
    })();
}
분명 클로저의 개념을 설명하고 그것을 바로잡는 예시인데, 바로잡지 못하고 있다.
예상 결과는 엘리먼트를 클릭하면 "Thanks for your click" 이 출력되고, 키보드를 누르면 "Thanks for your keypress" 가 출력되어야 하지만 위 예제를 실행하면 오로지 출력은 "Thanks for your undefined" 이 된다.

이유는  Closure 의 동작 방식 때문이다.
클로저는 값을 복사하는게 아니다.
스코프를 참조할 수 있는 것이고 따라서 위의 예제에서 i는 for문이 종료되며 2가 되고,
이벤트 핸들러에서 참조하는 배열은 items[2]가 되는데, 이것은 없는 참조이므로 undefined 가 되는 것이다.
올바른 코드로 고치려면  Closure 를 제대로 할당해줘야 한다.

var obj = document.getElementById("main");
var items = [ "click", "keypress" ];
for(var i = 0; i < items.length; i++) {
    (function() {
        // 변수를 참조해둬서 이벤트 핸들러의 클로저에서 접근할 수 있도록 한다
        var item = items[i];
        obj[ "on" + item ] = function() {
            alert( "Thanks for your " + item );
        };
    })();
}
이렇게 하면  Closure 에 item이라는 변수를 이벤트 핸들러들이 참조할 수 있게 되므로 정상적인 출력을 기대할 수 있다.
이 문제는 다음에 설명하는 privileged 메서드에서도 나타난다.
책 42페이지의 코드이다.

function User(properties) {
    for(var i in properties) {
        (function() {
            this["get" + i] = function() {
                return properties[i];
            };
            this["set" + i] = function(val) {
                properties[i] = val;
            };
        })();
    }
}
var user = new User({
    name: "Bob",
    age: 44
});
alert(user.getname());
alert(user.getage());

이 코드는 앞선 코드보다 더욱 위험한 폭탄 코드를 담고 있다.
객체에 자동으로 get, set 메서드를 추가해주는 코드이지만, 실제로는 window 객체에 해당 메서드를 추가하고 그것도 반환은 getname()을 호출하든,getage()를 호출하든 무조건 44만 반환하는 코드가 나온다.

글로벌 객체는 어느 경우에서든지 함부로 수정되어서는 안되는데 이것은 그냥 수정해버린다. 그것도 이벤트 핸들러와 함께.

위 코드에서

for(var i in properties) {
    (function() {
        // this의 잘못된 사용!!! 여기서의 this는 글로벌 객체, 즉 window이다.
        this["get" + i] = function() {
            // i 변수는 클로저 성격상 무조건 age를 가리키게 된다!!!
            return properties[i];
        };
        // 위와 마찬가지...
        this["set" + i] = function(val) {
            properties[i] = val;
        };
    })();
}

이 부분, this를 사용하는 부분이 크게 잘못되어 있다.
내부함수는 외부 함수의 컨텍스트를 따르지 않는다는 사실 때문이다. 내부 함수에서의 this는 글로벌이며 자연히 위 코드에서는 window["on" + items[i]]... 가 되어 버린다.

또한 클로저 스코프의 잘못된 활용으로 그나마도 모두 함수 호출 결과로 마지막에 세팅된 프로퍼티 값인 44만을 반환한다.

원하는 값을 얻으려면 다음과 같이 코드를 변화시켜야 한다.


function User(properties) {
    // this를 관례적인 이름인 that에 할당해둔다.
    // 이렇게 함으로서 내부 함수에서 이것에 접근이 가능하다.
    var that = this;
    for(var i in properties) {
        (function() {
            // 클로저의 참조를 변화시키기 위해  i를 prop에 할당해둔다.
            var prop = i;
            that["get" + prop] = function() {
                return properties[prop];
            };
            that["set" + prop] = function(val) {
                properties[prop] = val;
            };
        })();
    }
}

중요한 부분에서 저런 오타 코드를 싣고 있는다면 큰 문제가 될텐데;
아쉬운 부분이다.

하지만 책 내용은 참 알찬 편으로 자바스크립트를 공부할 마음이 있다면 적극 추천하고 싶다. 소소한 오타에 주의하면서 말이다. (사실 그렇게 많지도 않다.)

2011. 11. 28.

겸손하고 나대지 않는 자바스크립트, unobtrusive javascript.

개요



자바스크립트 관련 포스팅 및 서적을 읽으면 거의 등장하는 단어는 바로

"겸손한 자바스크립트"

라는 것이다.
하지만 뭔가 와 닿지 않는다...

언어가 겸손이라니???

보통 원문에는 unobtrusive javascript 라는 것으로 쓰여 있다.

unobtrusive??

단어의 뜻은 이렇다

  • unobtrusive [ʌ̀nəbtrúːsiv]  
    주제넘지 않은, 조심성 있는, 겸손한  
  • unobtrusive [|ʌnəb|tru:sɪv]  영영사전
    not attracting unnecessary attention

출처 - 다음 사전

보통 번역되는 겸손한, 나대지 않는 뜻이 맞다.
그래도 뭔가 와닿지 않는다. 좀더 생각해 보자.

일단 단어의 뜻을 해석하려면 그것이 사용되는 부분부터 이해하는게 보통 정확하다.

웹의 접근성



겸손한 자바스크립트를 거론할때 첫번째로 나오는 주제는 접근성이다.

자바스크립트는 웹의 중요한 이슈인 접근성에 어떻게 관여될까.

야후에 접근하는 사용자의 2%는 자바스크립트를 끈 사용자라는 통계가 있다.
야후같이 거대한 서비스에서는 2%는 엄청난 사용자다.

Internet Explorer, FireFox, Chrome, Safari, Lynx...

브라우저는 하나의 통일 플랫폼이 아니다. 여러 종류가 존재한다.
브라우저의 엔진에 따라서는 자바스크립트를 해석하지 못하거나 불완전하게 해석할 수도 있다.
또한, 스크린 리더기, 텍스트 브라우저같은 특수한 환경에서는, 자바스크립트는 동작이 어려울지도 모른다. 요새 각광받는 모바일 환경도 이 문제에서 예외가 될 수 없다.

시멘틱 웹



두번째는 시멘틱(semantic) 이다. 문서의 시멘틱이 중요해지는 요즘이다.
시멘틱 단어 그대로, 문서의 의미는 굉장히 중요하다. 이유중 먼저 떠오르는 것은 검색 엔진이다.
오늘날 검색 엔진에 노출된다는 뜻은 따로 말 안해도 잘 알거라 생각한다.

그러나, 현대의 대부분의 검색 엔진은 자바스크립트를 완전히 해석하지 못한다.

페이지에 static하게 박힌 <a>이나 <form> 에 있는 href, action 속성을 검색 프로그램 로봇이 읽어가는 것과,
스크립트에서 해당 태그에 클릭 이벤트를 캐치하에 스크립트에서 페이지를 이동하거나 폼을 서브밋하는 것중에 어느것이 더욱 검색 엔진에 더욱 노출이 잘 될까? 뻔하지 않을까.

케이스 A

<a href="/content/get_alright.html">Get All</a>
<form id="frm" action="/server/getData">
<input type="text" name="word" />
<input type="text" name="id" />
<button type="submit">검색</button>
</form>

케이스 B

<script type="text/javascript">
var anch = function(e) {
location.href = "/content/get_alright.html";
}
var submit = function(e) {
var f = document.getElementById("frm");
f.action = "/server/getData";
f.submit();
}
</script>
<a href="#" onclick="anch();">Get All</a>
<form id="frm">
<input type="text" name="word" />
<input type="text" name="id" />
<button id="s_btn" onclick="submit()">검색</button>
</form>

두개의 케이스를 보면 A는 문서 하나로서 완벽한 동작을 한다.
그러나 B는 그렇지 않다.
검색 엔진이 알아보기도 힘든 문서이며, 스크립트 사용이 불가능할 경우, 이 문서는 전혀 동작하지 않는다.

시멘틱으로서의 문서의 가치는 A쪽이 높다고 볼 수 있겠다.

진화



앞에서 접근성, 시멘틱 웹에 대해 다루었지만 또 하나 중요한게 있다.
Active-X와 자바스크립트 이 둘의 관계.
Active-X를 잘 알거라 생각한다.
Active-X가 과다하게 사용된 사이트가 나쁘다는건 누구나 알고 있다. 이것을 욕하는 개발자도 많다.
하지만 javascript 또한, Active-X와 개념적이나 동작 면에서 크게 다르지 않다!

대부분의 브라우저에 탑재 되어 있을 뿐이지 앞으로 어떻게 바뀌냐에 따라 자바스크립트는 없어지거나 할 수 있을지도 모른다. 자바스크립트 퇴출 운동이라...생각만해도 웃긴다.
모바일 환경에 접어들며 밀려버린 플래시의 예를 보면 뭐 답은 명확하다.
(앞으로 자바스크립트가 완전히 웹 표준에 정착되어 버리면 이야기는 다르겠지만...)

그리고 HTML의 태그의 기능이나 표현이 바뀔 수도 있다. 이럴 경우 문서에 강하게 유착된 스크립트가 오동작 할 위험도 있다. (이럴 정도로 크게 바뀔 일은 절대 없겠지만...가정이다 가정)

나대지 말자



위에서 든 2,3,4의 예가 일관되게 가리키는 것은 자바스크립트의 과용 이다.

처음 화제로 돌아가서 겸손한 자바스크립트에 대해서 말해보자

HTML문서가 해석될 때 스크립트가 관여하는 부분을 최소화하며, 혹시 동작하지 않더라도, 어느정도의 기본 기능은 제공해야 하며 앞으로 발전 방향에 무리가 없을 정도로 사용하는 자바스크립트를 말한다고 볼 수 있다.

이것을 전문 용어로

Graceful degradation (우아한 낮춤)
Progressive enhancement (점진적 향상)

라고 한다.

Graceful degradation
 은 스크립트 지원 레벨이나 여부를 검사하고 그것에 맞게 적용 범위를 낮춰가는 방법이다.
<noscript> 태그가 이러한 경우에 쓰일 수 있겠다

반대로 Progressive enhancement는 점진적으로 문서에 자바스크립트를 적용하는 방법이다.
원본 HTML이 있으면 먼저 기본 태그와 CSS만으로 문서를 작성하고, 자바스크립트를 그 위에 점진적으로 얹어가며 기능을 개선하는 방법이다. 물론 과용은 안되며 문서는 독립적이어야 한다.

결론



  • 겸손한 자바스크립트
  • 나대지않는 자바스크립트
  • 무간섭 자바스크립트
  • 최소 자바스크립트
  • 분리형 자바스크립트
  • 키다리아저씨 자바스크립트(?)

 ...어떻게 이름 붙여도 와 닿진 않는다.

그러나 웹의 변화는 너무 무쌍하기에 어떻게 바뀔 지 알 수 없다.
하지만 이런 개념을 바탕으로 자바스크립트를 다루는 것은 참 좋은 습관이 될 것이다.

2011. 11. 14.

간단한 정규표현식 테스터

정규표현식을 본격적으로(?) 공부해보고자...
전에 본 http://rejex.heroku.com/ 에 감명받아 비슷하게 구현해보았다.
기능은 안드로메다급으로 뒤쳐지지만...

아래는 정규표현식에 많이 쓰이는 자바스크립트 특수문자.
(출처 - http://iamnotokay.tistory.com/26)
  • \f 폼피드
  • \r 캐리지리턴
  • \n 새줄
  • \t 일반 탭문자
  • \v 세로 탭문자
  • \0 NUL널문자
  • [\b] 백스페이스
  • \s 공백문자 (\f, \n, \r, \t, \v, \u00A0, \u2028, \u2029)
  • \S 공백이아닌문자
  • \w 알파벳문자,숫자,_ [a-zA-Z0-9_]
  • \W 알파벳문자,숫자,_가 아닌문자 [^a-zA-Z0-9_]).
  • \d 정수(short for [0-9]).
  • \D 정수가 아닌 문자 (short for [^0-9]).
  • \b 단어의 경계 공백,새줄.
  • \B 경계가 아닌문자.
  • \cX 컨트롤+문자 E.g: \cm matches control-M.
  • \xhh 핵사코드
  • \uhhhh 유니코드

문자열에는 검사할 문자열을, 정규표현식에는 / 을 제외한 문자열을 넣는다.
입력시마다 이벤트가 체크되어 매칭 여부를 검사한다.

정규표현식 검사기
문자열 입력

정규표현식 입력 test method 로 수행

결과

예외를 거의 고려하지 않고 급조한 것이라 브라우저에 따라 잘 동작하지 않을 수 있다.
사용된 소스는 다음과 같다.

(function() {
    var doc = window.document;
    var intervalId;
    var eleText = doc.getElementById("input_text");
    var eleReg = doc.getElementById("input_regxp");
    var eleResult = doc.getElementById("javarouka_check_textarea");
    var isMethod = doc.getElementById("reg_method");
            
    function createRegexp() {
        return reg = new RegExp(eleReg.value);
    }
    function strMatch() {
        if(!eleText.value || !eleReg.value) return;
        try {
            console.log();
            if(isMethod.checked) {
                eleResult.value = createRegexp().test(eleText.value);
            }
            else {
                eleResult.value = eleText.value.match(createRegexp());
            }
        }
        catch(e) { 
            eleResult.value = "오류!!!\n" + e.toString();
        }
    }
    function bindEvent() {
        eleText.onkeyup = strMatch;
        eleReg.onkeyup = strMatch;
        isMethod.onclick = strMatch;
    }
    bindEvent();
})();