라파엘이 좀 손에 익으니, 금방 맹글게 되네..
오랜만에 삼각함수 검색했다. ㅇㅎㅎ

코드 좀 정리해서 모듈로 배포해야지.
샘플 페이지는 >>> 여기에

몇가지 코드 정리할 필요가 있다.
1) 옵션을 뺄수있는 설정들 정리
2) 주석 정리
3) 모듈 테스트

주석빼면 100줄도 안되네.. ㅇㅎㅎ
2011/12/07 20:59 2011/12/07 20:59

Trackback URL : http://miconblog.com/tc/sohn/trackback/685

오늘 미투머니 테스트 코드를 작성하다가,
그동안 안개속에 쌓였던, 정규식의 맘을 헤아리게 되어 몇가지 공유합니다.
제가 말하고자 하는 내용은 사실 이 포스트에 다 있어요.

정규식 RegExp Vs String


정규식은 기본적으로 문자열 패턴을 응용한 놈입니다.
그러니, 당연히 String 객체와는 뗄레야 뗄수가 없는 놈입죠~

그 동안 크게 신경안쓰고 있다가, 아~ 그렇치 하는걸 오늘 알았어요. 저도..ㅋㅋㅋ
너무나 당연한 사실을 저만 늦게 깨달은 기분.. 아하하하

정리하면,

정규식은 2가지 형태로 사용할수있습니다.
1. 정규표현식 객체(RegExp)를 사용하는 방법과,
2. 문자열 객체(String)의 정규식 메소드를 이용하는 방법

메소드만 정리하면, 이렇쵸!!
RegExp.test() - Boolean 값을 리턴
RegExp.exec() - 매칭된 값을 Array로 리턴
String.split() -
String.match() -
String.replace() -
String.search() -

RegExp.test() Vs RegExp.exec() 의 성능 차이


일 반적으로 두 메소드 중에서, test() 메소드의 성능이 더 좋다고 얘기합니다.
왜 그럴까요?.. 그냥 성능이 좋다고 하니까 그려려니 하나요? 이젠 제대로 이해해봅시다.
여기엔 이유가 있습니다.

바로 캡쳐링이라는 기능 때문입니다.
캡쳐링이 뭐냐하면, 간단히 말하면, 패턴으로 찾은 놈을 따로 저장하는걸 얘기합니다.

앞의 두 메소드의 리턴값이 하나는 Boolean 이고 하나는 Array인 점이 바로 여기에 있습니다.
당연히, test 메소드가 리턴값이 Boolean 이므로, 성능이 더 좋겠죠?
네,.. 맞습니다. 하지만 단순히 리턴값의 사이즈가 작다고 성능이 더 좋다고 얘기할수는 없습니다.

왜냐면, 결국 RegExp 객체가 찾은 패턴을 모두 가지고 있기 때문이죠.
따라서, test() 메소드만을 사용한다고 성능이 좋아지는 것은 아니랍니다.
정확히 얘기하면, 어떻게 패턴을 정의하냐에 따라 달라지겠죠!!

예를 들어보겠습니다.
var str = "테스트 테스트1 테스트2 테스트3 테스트4";
var regx = /테스트\d/;

regx.test(str);   // true
regx.exec(str);  // ["테스트1"]
위와 같이 실행하면, test()는 true, exec()는 ["테스트1"] 배열을 반환합니다.
그리고 여기서 하나더!! 위에서도 잠깐 언급했지만, 두 메소드 모두 결국 RegExp 전역 객체를 사용하게 됩니다.

그러니까, 각 메소드를 실행하고, 아래와 같이 RegExp 객체의 $_ 와 $1 값을 확인해보면,
모두 같음을 확인할수있습니다.
RegExp.$_  // 테스트 테스트1 테스트2 테스트3 테스트4"  - 테스트할 문자열
RegExp.$1  // ""  - 캡쳐링된 문자열

이 얘기는 결국, test() 메소드와 exec() 메소드의 내부 구현은 같되, 리턴값만 다름을 의미합니다.
즉, 현재 까진 리턴 사이즈 말고는 성능이 같다는 얘기죠~!!

이제 좀 변형해봅시다. RegExp 객체에 패턴을 저장하기 위한 캡쳐링 옵션을 줘보도록 하지요.
regx = /(테스트)/ 로 바꿔서 해봅시다.
var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(테스트\d)/;

regx.test(str);   // true
regx.exec(str);  // ["테스트1", "테스트1"]
RegExp.$1;       //  "테스트1"    - test(), exex() 모두 같음.
뭐가 다른지 감이 오나요?.. exec()가 뱉어내는 리턴값을 유심히 보세요. 아직 모르시겠나요?
캡쳐링은 매핑된 결과를 RegExp 객체가 내부에 저장한다고 앞서 말씀 드렸습니다.
바로 그 캡쳐링된 결과를 RegExp.$1, RegExp.$2 등으로 읽어올수있습니다.
물론 캡쳐링은 하나의 패턴안에서 괄호를 여러번 사용함으로써 저장할수 있습니다.

가령, 이렇게 쓸수도 있다는거죠!!
var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(테스트)\s(테스트\d)/; 

regx.exec(str);   // ["테스트 테스트1", "테스트", "테스트1"]

자 이제 exec()의 리턴값의 구성을 이해하시겠죠?

[매칭된 문자열, 캡쳐링된 첫번째값, 캡쳐링된 2번째값, 캡쳐링된 3번째 값, ... , 캡쳐링된 N번째 값]

이젠 RegExp에 저장하지 않도록 비캡쳐링(?:xxx) 옵션을 주고 하나 더 해보죠.
var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(?:테스트\d)/;

regx.test(str);   // true
regx.exec(str);  // ["테스트1"]
RegExp.$1;       //  ""    - test(), exex() 모두 같음.
자~~ 이젠 어떤 차이가 있지는 아시나요? 여전히 모르시겠다구요?
비캡쳐링(?:) 옵션을 사용해서 RegExp 에 찾은 패턴값을 저장하지 않았습니다.
그러니까, exec() 리턴값에도 찾은 패턴값이 넘어오지 않게 되죠?

자 그럼, exec() 리턴값의 구성을  한번더 정리해보죠~

[매칭된 문자열, RegExp.$1, RegExp.$2, RegExp.$3, ... , RegExp.$N]

마지막으로 그럼,

도대체 성능과는 무슨 관계가 있는거냐?

이 질문에 답할 시간입니다.
이미 눈치채고 계신분이라면, 조용히 닫기버튼을 누르셔도 됩니다. ㅋ

test()와 exec() 메소드의 성능차이는 간단합니다. 이렇게 정리하도록 하죠!

"간단한 패턴 문자가 있는지 없는지를 확인할땐, 비캡쳐링과 test() 메소드 조합을 사용해라!
"

되셨 나요?

이렇게 얘기 할수도 있습니다.

"test() 메소드를 사용할때는 반드시 비캡쳐링 패턴으로 정의해라!"

그 이유는 앞서 주구장창 설명한 캡쳐링되어 저장된 값 때문입니다.


패턴 플래그 g 에 대한 고찰


이제 오늘 깨달은 것의 하일라이트!! 바로 g 플래그 옵션입니다.
패턴 플래그는 총 3가지가 있죠. i, g, m 뭐 다 아실꺼라 생각하고 각각의 설명은 생략합니다.
모르면 검색해보아요~

이제 g에 일반적으로 알려진 사실을 흔히 쓰는 예제로 보면, 아래와 같습니다.
var str1 = "No pain, No gain!";
var str2 = str1.replace(/ain/g, "XXX");
// str2 = "No pXXX, No gXXX!"
즉, "g 옵션을 쓰면, 모든 패턴을 찾게 된다." Global 의 G 가 바로 그 g 옵션인거죠..

그러면, 얼핏 이런 사실을 알고 있을때, 지금껏 제가 착각해왔던 것은 아래와 같은 겁니다.
앞에서 해왔던 예제를 이어봅니다.
var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(테스트\d)/g;

regx.exec(str);  // ["테스트1", "테스트2", "테스트3"] 일까요???????
g 는 글로벌 옵션이니까,.. 저렇게 나와야 하는게 아닐까?.. 하는거죠!!
결론부터 얘기하면 아닙니다...
왜인지는 아시겠죠? 아직도 그 이율 모르신다면, 앞에서 설명한 exec() 메소드의 리턴값 구성을 다시한번 보세요.

그러면 이렇게 해보죠..
var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(테스트\d)/g;

regx.exec(str);  // 1번 실행, ["테스트1", "테스트1"]
regx.exec(str);  // 연속해서 두번 실행, ["테스트2", "테스트2"]
regx.exec(str);  // 연속해서 세번 실행, ["테스트3", "테스트3"]
RegExp.$1;  // "테스트3"
어랏?.. 저렇게 나올껄 예상하셨나요? 올~~ 예상했다면, 당신은 규식이를 잘 하는 분입니다.
아직도 모르시겠다구요? 글로벌 옵션이긴 하지만, 적어도 제가 기대했던 것과는 사뭇 다릅니다.

자 그럼 g 옵션의 비밀을 정리해보죠..
정리하면,

패턴 플래그 g 옵션은 연속해서 패턴을 찾을때,

다음 패턴 검색을 위해 RegExp객체에 패턴 검색의 시작 위치를 저장해 둔다.


이해되셨나요? 앞에 예제를 곱씹어보세요~ ^^

test() 메소드와 exec() 메소드는 리턴값이 다른 만큼 분명 그 쓰임도 다릅니다.
따라서, 패턴을 어떻게 정의하느냐에 따라서,
RegExp 객체에 얼마나 많은 정보가 캡춰링 되어 저장되는지, 그 비효율성도 고민하셔야합니다.

여기까집니다.
도움이 되셨나요? 도움이 되셨다면,.. 리플이라도..ㅋㅋ
아놔.. 이거 다 아는거잖아.. 썅~ 뭐냐? 이러면,.. 걍 쌩까세요~ ㅋㅋㅋ

2010/06/15 10:46 2010/06/15 10:46

Trackback URL : http://miconblog.com/tc/sohn/trackback/617

  1. 불꽃남자의 생각

    Tracked from miconblog's me2DAY 2010/06/14 01:04 Delete

    잠깐 외출했다 와서, 아까 쓰던걸 이제야 정리끝~~

일단, 발행하기 전에 정리부터 하자..
예제는 나중에 정리하고.. 생각나는대로 써보면,..
대충 정리해보면 아래와 같다.

1. DOM 노드 캐싱
DOM을 캐싱할때는,.. 캐싱하려는 노드를 cloneNode(true)로 떠서 캐시하자..
그렇치 않으면, IE에서는 innerHTML로 해당 노드를 날렸을때,. 캐시한 노드까지 날라간다.
반면, IE외의 다른 브라우저에서는 innerHTML로 해당 노드를 날렸을때..
해당 노드만 날라가고, 캐시한 노드는 그대로 있다.
[하나 더 주의] - cloneNode를 할 경우, 바인딩된 이벤트는 복사되지 않는다.!

2. 메모리 누수
모든 브라우저가 innerHTML로 엘리먼트를 교체하면, 엘리먼트가 걸려있는 이벤트 핸들러들이
날아가 버린다. 보다 정확한 의미는 참조를 잃어버린다!!
때문에 참조를 잃어버린 이벤트 핸들러들은 영영 해제할 길이 없어 메모리 누수가 생긴다.

따라서, 엘리먼트 교체를 해야할 경우엔, 이벤트 핸들러를 먼저 제거해주거나..
앞에서 얘기한 노드를 캐싱하거나..
아니면, HTML로 날릴만한 엘리먼트엔 이벤트 핸들러를 걸지말고, 그 상위 노드에 걸어서
버블링 처리를 해라~!!

3. HTML 렌더링
HTML을 innerHTML로 재할당을 할경우, 브라우저는 재할당한 부분만 랜더링을 다시하게 된다.
이때, 구식브라우저(꼬진브라우저, 느린브라우저)인 IE6에서는 랜더링 할때, 생각보다 속도가 느리다.
그래서 아래와 같이 사용하게 되면,..
node.innerHTML = "<div>....중간생략 복잡한 디비전 태그 교체...</div>"
doSomethingHere();
doSomethingHere() 함수 안에서 새로 랜더링 되는 엘리먼트의 width나 height 등을 가지고 뭔 짓꺼리를 하게 되면,
IE6에서는 htmlfile 에러는 내뱉는다.

이유는 앞서 설명했듯이 랜더링 속도가 느려서 발생하는 문제로...
로드 된후, 실행시 까지 약간의 딜레이를 주거나,
인터벌울 돌려서 렌더링이 됐는지 체크를 한다.

난 걍 랜더링시 문제가 될경우에 한에서만 이렇게 쓴다.
el.innerHTML = sTpl;
var oInstance = this;
setTimeout(function(){
          oInstance._initialize();
}, 100);

4. htmlfile 에러
그밖에 IE6혹은 IE7에서 htmlfile 에러가 발생하는 경우가 있는데 아래와 같은 경우가 있다.

  1. 붙이려는 노드가 읽기 전용 속성의 태그이고, 거기에 ID 값을 부여해서 innerHTML 할 경우
    ** IE에서 COL COLGROUP FRAMESET HTML STYLE TABLE TBODY TFOOT THEAD TITLE TR 개체등은 ID 필드가 읽기전용 속성이고 그 외의 개체에서는 모두 읽기/쓰기가 된다.
  2. 기본 HTML 구조 규칙을 깨는 경우,
    예를 들면, LI 엘리먼트 하위에 다시 LI 노드를 자식으로 넣는 경우가 있겠다.
    이 경우는 실제로 겪었던 부분으로, li 자식 노드로 li를 넣는 바보같은 짓을 범했다. 이런 경우가 흔하지 않는 경우인데, 안타까운건 FF 에서는 li 하위에 li 를 짚어 넣어도 아무런 에러를 발생시키지 않는 다는 것이다. 

5. FireBug를 믿지마라!

보통 FireFox의 플러그인 인 firebug 를 이용해서 디버깅을 하게되는데, FF 는 너무나 관대(?)해서,
IE에서 뱉는 에러를 몇몇가지를 그냥 쌩까고 혹은 아~ 이건 개발자 실수니까,..스마트(?) 하게 알아서 정정해준다.
즉, FF만 믿고 개발했다가는 IE 호환성에 적신호를 받게 될것이다.
그렇다고, firebug 가 구리다는건 아니고, 간간히 중간중간 때때로, 생각날때, IE 브라우저로 테스트를 해보는 습관을 갖는게 좋겠다.

추천하는 바는 그냥  IE6 브라우저를 버리고 갔으면 한다. (그냥 개인적인 바램~ OTL... ㅜㅜ )
하지만 현실은 시궁창... ㅎㅎ
2009/07/26 14:39 2009/07/26 14:39

Trackback URL : http://miconblog.com/tc/sohn/trackback/567

  1. IE에서 innerHTML 사용시 주의해야할 점

    Tracked from Spring..그환상의 대하여 2009/09/08 10:45 Delete

    1. DOM 노드 캐싱 DOM을 캐싱할때는,.. 캐싱하려는 노드를 cloneNode(true)로 떠서 캐시하자..그렇치 않으면, IE에서는 innerHTML로 해당 노드를 날렸을때,. 캐시한 노드까지 날라간다.반면, IE외의 다른 브라우저에서는 innerHTML로 해당 노드를 날렸을때..해당 노드만 날라가고, 캐시한 노드는 그대로 있다. [하나 더 주의]

웹개발자나 UI 개발자에게 없어서는 알될 툴중에 하나가 바로,..
HTTP 모니터링툴일것이다.

대표적으로 HttpWatchFiddler 라는 Http Debugging Tool 이 있다.

두개 모두 내가 유용하게 쓰고 있는 툴인데,..
그중에서 피들러가 어느날 갑자기 너무 느려졌다..
피들러를 열고, 웹페이지 하나 열라치면,.. 몇 십초나 걸린다.-_-;..

도대체 뭐가 문제인거야? 한참을 고민하다.. 에라이~ 모르겠다..
접어두고, 그냥 불편한대로 다른 방법을 이용했는데..
그런데, 이젠 더이상 안되겠다 싶어...
Fiddler 관련 메일링 리스트를 뒤졌다..

역시 찾으니까 금방 나온다..-_-;..
인터넷옵션 - 연결 - LAN 설정에서 자동 구성의 체크박스를 모두 해제해두면 그걸로 끝!

다시 피들러가 제속도를 찾을것이다.
사용자 삽입 이미지

















그래도 안된다고?.. 그럼 나도몰라!! ㅋㅋ
2009/04/29 12:48 2009/04/29 12:48

Trackback URL : http://miconblog.com/tc/sohn/trackback/552

항상 이런 이슈는 퇴근쯔음 생긴다..-_- 싫다 정말..

여튼, 개편이후, PV가 갑자기 줄어서.. 확인해본결과..
문제는 이미지 태그의 src 이용해 리퀘스트를 날릴때, 그 리퀘스트가 중지되는 현상이 발견됐다.
Httpwatcher에서 확인해본결과.. 

아래와 같은 코드 사용시에..
(new Image()).src = "로그집계기록" 

열에 2~3번은 aborted가 되고 있었다. 
위와 같은 상황이라면 PV가 대략 20~30%는 하락할것이다..ㅎㅎ

여튼 수많은 삽질을 통해 대강의 원인을 찾아본결과..
아래와 같은 이유가 있었다.  (이유는 3가진데.. 자세한건 아래 링크 참조)

http://blog.httpwatch.com/2008/01/28/w ··· watch%2F

그중에서 다운로드 중일때, 리퀘스트를 날리면 요청을 끊어버린다는 것!!
이게 가장 유력한 원인으로 파악되고 있다. 

일단.. 내일 다시 테스트해봐야징.. 졸립넹..-_-
-----

최종정리~

위에서 발생한 문제는 최종적으로 가비지 콜렉션 문제로 정리됐다. 
즉, 위와 같이 (new Image()).src = "로그집계 기록"을 호출할경우..
요청에 대한 응답을 받기도 전에 이미지 객체가 가비지 콜렉션 되어 사라질수 있다. 
이럴경우, 응답은 취소가 된다. 

해결책은 간단하다. 가비지 콜렉션 되지 않도록 이미지의 참조 카운터를 누군가가 들고 있으면 된다. 아래와 같이 전역변수로 가지고 있어도 되고,

_gImg = [];
function 로그집계 = {
    var o = new Image(); 
    o.src = "로그집계";
    _gImg.push(o);
}
로그집계();

혹은 아래와 같이 abort 이벤트에 대해서 이벤트를 바인딩 해둬도 된다 
function 로그집계 = {
    var o = new Image(); 
    o.src = "로그집계";
    o.abort=function(){};
}
로그집계();

후자보다는 전자가 보다 명확한 방법이다. 
후자의 경우의 코드상의 의미는 abort 이벤트가 발생했을때, 어떠한 처리를 따로 해주는것인데..
저렇게 아무런 의미없는 함수를 바인딩해둘필요는 없지 않을까?

그래서 전자로 처리했다. 
2009/01/09 02:06 2009/01/09 02:06

Trackback URL : http://miconblog.com/tc/sohn/trackback/502

imeMode 속성은, IE 전용 속성인데,
FF3 에서부터는 해당 속성을 지원해주는듯하다..
https://developer.mozilla.org/En/CSS/Ime-mode

하지만.. 테스트는 안해봤다. 테스트 해본 사람들에 의하면,..
아직 안된다고 하더라..ㅎㅎ

그래서 좀더 크로스 브라우징이 가능한 방법이 없을까?
생각하다가.. Flash 인터페이스를 이용하는건 어떨까 생각해봤다.
아래 링크를 참조하고..
http://kimkijeung.com/entry/IME-Input-Method-Editor


위 링크처럼 가능하다면, 플래시컨테이너 하나 로드해놓고,
인터페이스 함수만들어서, 자바스크립트로 해당 함수를 호출하면
간단히 해결될것같은데..

오늘 집에가서 한번 만들어봐야겠당..ㅋ

반대로, FF에서 한글을 입력 안하게 하는 방법도 있을수 있겠다
편법이지만..
http://blog.hooriza.com/1049

2008/12/15 18:37 2008/12/15 18:37

Trackback URL : http://miconblog.com/tc/sohn/trackback/489

앞으로 오픈베타까지 5일 남았다.
그동안 반 미친듯이 코드만 짜다가, 요즘은 막바지라 한결 여유를 찾은듯하다.
어제부터 코드 프리징을 위한 막바지 최적화 작업에 들어가고 있다.

오늘은 여유롭게, 그동안 못한 블로깅을 하면서, 얘들은 먼 얘기를 하고 있을까..
검색을 좀 해봤다. ㅎㅎ
많은 사람들이 기대반 우려반, 그리고 일단 까고보자 반, 아무생각없다 반인거 같다. ㅎㅎ
머 결국 어느정도 관심은 있다 정도로 정리가 되는군.
머 일단, 각설하고...

개편되는 메인페이지 로딩속도를 최적화하기 위해 고민한 것들을 공유해본다.

사용자에게 있어서 첫 페이지 로딩속도는 굉장히 크리티컬한 요소다.
그렇기 때문에 최적화 작업을 하면서, 가장 신경썼던 부분이 바로 요청회수 줄이기였다,

클라이언트 영역에서 요청횟수를 줄일수 있는 방법은 3가지로 볼수있겠다.
외부링크로 걸려있는 css 파일 갯수 줄이기..
외부링크로 걸려있는 js 파일 갯수 줄이기..
그리고, 이미지 갯수 줄이기..

하지만, 실상 요청횟수 줄이기가 만만치는 않았다.
특히, 이미지가 주가 되는 대부분의 포탈이나 웹사이트의 경우는 정말 불가항력에 가깝지 않을까?

그래도 뭐 어떻게든 요청횟수를 줄이기위해, CSS Sprite도 적용했지만, 접근성 문제로 인해,
전면적으로 CSS Sprite을 적용할수는 없었다.
"CSS Sprite은 의미없는 배경 이미지에만 적용할 것을 권한다."
CSS Sprite은 검색을 해보면, 아마도 구글 코리아 메인 페이지의 애니메이션 효과를 예제로 들고 있는 글들을 쉽게 접할수 있다. 바로 구글 코리아가 적용한 애니메이션 효과가 대표적인 의미없는 배경이미지가 되겠다.
 
여기엔 단순히 접근성 문제만은 아니고, 대부분 캐시 서버에 이미지를 올려놓기때문에, 운영이슈도 함께 고려되어야한다. 이미지에 수정사항이 생기면, 전체 그리드 이미지를 새로 업로드하고, 경로를 수정해줘야한다. 안그럼 캐시된 사용자의 브라우저에선 제대로된 이미지를 볼수없을테니 말이다. 이럴때마다, 정말 구글과 같은 텍스트 기반의 웹사이트들이 어찌나 부럽뜬지..ㅎㅎ
그러나 , 여기는 한국이고, 구글이 우리회사도 아니다! ㅎㅎ

한편 모듈별로 작업하면서 크고작은 최종 js 파일의 갯수는 총 20개나 됐다.
물론, 배포툴을 이용해 파일을 2개로 합쳤고, 압축을 하지 않았을 경우, 대략 200KB 정도의 크기였다.  그리고 압축을하고, 아파치 gzip 모듈을 적용하면, 대략 40KB 정도의 크기가 나왔다.
요청횟수는 1/10로 줄였고, 파일크기는 대략 1/5 정도로 줄였다.

과연 이렇게 최적화한 결과는 어떠했을까?
FF3 의 FireBug 를 통해서, 대략적인 로딩 시간을 비교해봤다.

[그림으로 넣으려고 했으나.. 귀찮아서 생략하고 대략의 결과는 다음에...ㅋㅋㅋ]






2008/12/10 03:17 2008/12/10 03:17

Trackback URL : http://miconblog.com/tc/sohn/trackback/486

파이어 폭스 일명, FF 로 통하는 이 브라우저에는 엄청난 디버거 툴이 있다.
바로 FireBug 라는 놈인데, 플러그인으로 붙어서, 갖가지 일을 해준다.

내가 이제까지 써본 디버거 중의 최고는 단연 비쥬얼 스튜디오 2005 였지만,
브라우저라는 특이한 상황에서는 역시, FireBug 만한게 없는거 같다.

반면, MS 계열의 인터넷 익스플로러는 스크립트 디버거 라는 놈이 존재한다.
사실 스크립트 디버거는 쓸모딱지가 없고, 스크립트 에디터라고 오피스 프로그램에 탑재된 놈이있다. 이것을 깔면, 약간은 비쥬얼 스튜디오 틱한 디버거 환경을 사용할수있다.

머, 내가 오늘 왠만한 UI 개발자라면, 다 아는 얘기를 꺼내놓고 싶어서 떠드는건 아니다.
그냥 오늘 하루종일 삽질한, 그 민감한 사안에 대해 얘기해보고자한다.

당신의 주무기는 무엇입니까?

나는 주로 IE6 기반에서 개발을 시작한다. 왜냐면, IE6이 가장 많은 브라우저 점유률..
특히 우리나라에서는 거의 95% 이상의 IE계열이고, 그중에서 IE6이 또 가장많은 점유률을 찾이 하고 있으므로, 어떻튼간에 난 IE6을 지원해야하는 수밖에 없다. 그게 내가 하는 일이니까..

그런데, 요며칠, 그놈의 IE8 때문에, IE8 beta2를 깔고 나면서 부터, 평소 하던 개발 스타일에 변화가 생겼다. IE8에 엄청난 브라우저 버그들을 탑재하고 있어서, 아무리 좋은 개발툴을 IE8이 기본으로 장착하고 있어도, 사용을 할수가 없었다.

그래서, 어쩔수없이.. 주무기를 FireBug로 갈아탈수밖에 없었다. IE8을 깐이상 지우기전까지는 IE6으로 돌아갈수도 없었다.

파이어폭스 과연 얼마나 똑똑한가?

그렇게 한참을 개발하고, QA에 임박한 상황이다. 그러나...그런데.. 흐흑...
FF 계열에서 잘 돌아가던 놈이, IE6에서 돌아가지 않는다. (결국 IE8은 지워버렸다..)
이놈때문에 하루 종일 삽질했다. -_-

결론, 문제는 이놈의 FireFox 가 너무나 똑똑해서,.. 혹은 반대로, IE가 너무나 민감해서..
라는 결론이 난다.
즉, FireFox에서는 대표적으로 아래와 같이 배열의 마지막에 콤마를 찍어도 문제가 없다.

배열 = [  A, B, C, D, ]

하지만 위처럼 마지막 인덱스에 콤마를 찍어버리면, IE에서는 어김없이 에러는 내버린다.

이런 특성, 어떻게 봐야하나? 누가 더 좋다고 할수있나?...
저 콤마때문에 빈번히 삽질을 하는경우가 많다. 특히나, JSON으로 통신을 하다보면,
서버쪽에서 실수로 저런 콤마를 하나 넣었을경우, 그 사실을 모르는 우리들은 하루중일 원인을 파악하느라.. 삽을 들수밖에 없다.

두번째,..
비슷한 내용인데, 이번에 태그와 관련이 있다.

파이어폭스에선, 아래와 같이 사용해도, 특별한 문제없이 랜더링을 해준다.

<a href="#" class='nvc_happy ><span></span>이거 문제 없어요.!!</a>

하지만, IE에서 위와같이 쓸경우, 역시나 에러는 아니고, 랜더링을 제대로 못해준다.
왜냐,.. class 이름을 적을때, 닫는 싱클쿼트 하나가 빠져있기 때문이다.

FireBug로는 저런 문제를 잡아낼수가 없다. 왜냐? FF에서는 문제가 안되기때문이다.

저걸 또 잡아내기란 쉽지가 않다. -_- (난 어떻게 잡아낸걸까? ㅎㅎ)

스크립터 에디터를 통해 스택을 하나하나 밟아가며,.. 결국 찾아냈다.
IE 만세~!!

오늘부터 나 다시 IE6 + Script Editor 로 주무기를 바꿨다!!

2008/11/18 01:47 2008/11/18 01:47

Trackback URL : http://miconblog.com/tc/sohn/trackback/482

오늘까지 발견한 4종의 버그..
1. resize 이벤트가 발생하지 않는다.
2. division의 offsetLeft 값이 이전 ie7이하 버전과는 다르게 계산된다.
3. 동적으로 생성한 노드의 DOM을 제대로 랜더링 하지 못한다.
4. display:none/block 으로 토글 할때, 처음 한번만 동작한다.

앞으로 계속 이페이지에 추가하고,
해결된 버그는 삭제할 예정~!!

제발 IE8은 지대로 만들어서, 모두 IE8로 갈아탔음 좋겠당...
2008/11/02 23:19 2008/11/02 23:19

Trackback URL : http://miconblog.com/tc/sohn/trackback/477

이 부분을 번역할까 말까 한참을 고민했다.
대략 A4 한장 분량의 번역작업이 대충 2시간이 걸렸기 때문이다.
머이리 오래 걸려? 라고 묻는자에게 말하고 싶다.. 당신이 해봐~!! -_-

내가 알고 번역하는 것과,.. 모르고 번역한 것은 천지 차이다.
주위에 한글 번역서만 봐도 이건 금방 안다. 대부분이 아마도 원문을 보라고 권할 것이다.

나도 그렇다. 내가 모 취미반 공부반으로 번역은 하고 있지만, 원문을 꼭 다시 볼것을 권하는 바이다. 분명 이렇게 이야기 해도 안보는 사람들을 위해, 최대한 메끄럽게 번역할려고 노력중이다.
왜냐면, 나도 나름 영문꽈 학위 받은 남자기 때문에.. ㅋㅋㅋ
작년말에, 영한번역 수업을 들어놓은게 참 많이 도움이 되는거 같다..

여튼 각설하고,.. 시작해보자!!

원문 번역: http://www.jibbering.com/faq/faq_notes/closures.html
A4 용지로 출력하면, 15페이지중에 7번째 페이지가 되겠다!!

자동으로 일어나는 가비지 콜렉션

ECMA 스크립트는 자동으로 일어나는 가비지 콜렉션을 사용한다. ECMA 262 스펙 명세서에는 어떻게 가비지 콜렉션이 작동하는지에 대한 자세한 정의는 없지만, 일반적으로 "어떤 객체가 자신을 가르키는 참조자가 없을 경우에 그 객체는 가비지 콜렉터에 의해 가비지 콜렉션될수 있다." 라는것이 일반적인 내용이다. 그러나 가비지 콜럭터에 의해 어느순간 콜렉션이 실제로 일어나는지 일어나지 않는지는 장담할수 없다.

(역주) 첫단락 해석이 이상해 의역을 많이 했다. 그리고 현재 ECMA 스펙을 확인중이다. 실제로 구현에 대한 아이디어가 있는지 없는지.. 확인 해봐야겠다..ㅋㅋㅋ

ECMA 스펙을 한창 보았다. 물론 처음부터 끝까지 보고 있는건 아니고, 필요한 부분만 참조해서 봤다. 그런데.. 오호라~ +_+_+_+ 스펙문서가 역시나 도움이 많이 됐다. 몰랐던 구현에 관한 아이디어가 너무나 많다. 물론 가비지 콜렉션에 대한 내용은 없었다. ㅋㅋㅋ

스펙문서를 보고, 처음에 이 문서 번역을 하면서 난해했던 부분이 상당부분 이해가 되고 있다 +_+_+ 이것도 차차 정리해둬야겠다.. 아~ 고수의 길은 멀고도 험하구낭..여튼 이어서~!!

클로져 만들기

클로져는 함수 객체가 리턴 되면서 형성되는데, 여기에는 2가지 조건이 있다.
먼저, 중첩된 함수가 다른 객체의 프로퍼티로 참조되거나 아니면, 전역변수나 전역적으로 접근 가능한 객체 혹은 외부 함수(중첩된 함수를 감싸는 함수)의 인자로써 넘겨지는 객체로 참조 되어질때, 클로져가 만들어진다. 아래 예제를 보자.

function exampleClosureForm(arg1, arg2){
    var localVar = 8;
    function exampleReturned(innerArg){
        return ((arg1 + arg2)/(innerArg + localVar));
    }
    /* return a reference to the inner function defined as -
       exampleReturned -:-
    */

    return exampleReturned;
}

var globalVar = exampleClosureForm(2, 4);

위에 exampleClosureForm 실행문맥 안에서 생성된 함수 객체는 가비지 콜렉션 되지 않는다. 그 이유는 전역변수에 의해 참조 되고, 여전히 접근 가능하기 때문이다. 또한, globalVar(n) 과 같은 형식으로 호출되어 실행할수도 있다.

하지만 위에 경우는 새로 생성된 실행 문맥 안에서 만들어진 함수객체가 globalVar 변수에 의해 참조되고 있고, 그 함수 객체의 [[scope]] 프로퍼티가 스코프 체인의 Activation/Variable 객체를 가르키고 있기 때문에 약간 복잡한 경우에 해당한다. 즉, 새로 생성된 exampleClosureForm 실행 문맥의 스코프 체인의 Activation/Variable 객체 역시 가비지 콜렉션이 되지 않는다.

현재 클로져가 형성되었고,위에 중첩된 내부함수 객체는 자유로운 변수들( 역주: free variables가 무엇을 의미하는지 정확히 알수 없으나, 다른 일반 변수들 보다 제약이 적다는 의미로 해석된다. 그런데 왜 저럽게 free variables라고 표현했는지 아직은 모르겠다. )과 스코프 체인에 있는 Activation/Variable 객체를 가진다.

그 Activation/Variable 객체는 globalVar 변수에 의해 참조되는 함수 객체의 [[scope]] 프로퍼티에 의해 참조되어 묵여 있는 상황이다. 그래서 이 Activation/Variable 객체는 그것의 프로퍼티의 값들이 그대로 보존된 상태로 있다. 중첩된 함수를 호출하는 실행문맥 안에서의 스코프에 대한 해석은 식별자로 해석을 한다.  즉, Activation/Variable 객체의 프로퍼티들이 식별자가 된다는 의미다. 그리고 이것은 여전히 set 과 put 두 가지를 모두 수행할수 있고, 실행문맥이 끝나더라도 여전히 존재하게 된다. 왜냐?.. 클로져로 묵여있는 상황이기 때문이다.

위 예제에서 외부 함수(내부 함수를 감싸는)가 리턴될때, Activation/Variable 객체는  공식적인 파라메타 그리고 중첩 함수 정의와 지역 변수로써 대표되는 상태를 가지고 있다. 즉, Activation/Variable 객체의 arg1 프로퍼티는 값 2 를 가지고 있고, arg2 프로퍼티는 값 4를 그리고  localVar 변수는 값 8을 그리고 exampleReturned 프로퍼티는 중첩된 내부 함수 객체를 가르키는 참조자를 가지고 있다. (앞으로 요 Activation/Variable 객체를 편의상 "ActOuter1" 이라고 부를것이다.)

만약에 exampleClosureForm 함수가 아래처럼 다시 호출되면,

var secondGlobalVar = exampleClosureForm(12, 3);
새로운 Activation 객체를 가지는 실행문맥이 만들어지고, 새로운 함수 객체가 리턴된다. 그 리턴되는 함수 객체의 [[scope]] 프로퍼티는 바로 이 두번째 실행문맥 안의 Activation 객체를 참조한다. 여기서 이 Activation 객체의 arg1 프로퍼티는 12, arg2 는 프로퍼티는 3를 갖는다. ( 편의상 앞으로 요 Actiovation 객체를 "ActOuter2" 라고 부를 것이다. )

여기서 명확하게 두번째 클로져가 exampleClosureForm 의 두번째 실행문맥에 의해 형성되었다.

exampleClosureForm의 실행으로 각각 globalVar 전역변수와 secondGlobalVar 전역변수에 생성되어 할당된 두 함수 객체는  표현식 ((arg1 + arg2 ) / (innerArg + localVar)) 을 리턴한다. 여기서 클로져의 값과 그 사용은 4개의 식별자에 따라서 작동하고 이 식별자들은 각각 엄격하게 해석되어진다. (역주: 즉, 왜 그렇게 해석되는지.. 나름의 이치가 있다는 얘기구낭.. )

전역 변수인 globalVar 변수에 의해 참조되는 함수를 globalVar(2) 를 통해 실행한다고 생각해보자. 새로운 실행 문맥이 만들어지고, 또 하나의 Activation 객체(앞으로 이것을 "ActInner1" 이라고 부를 것이다.) 생성된다. 이 객체는 스코프 체인 맨앞에 삽입되고, 실행된 함수 객체의 [[scope]] 프로퍼티에 의해 참조된다.  이 ActInner1 은 innerArg 라는 이름의 프로퍼티가 주어지고, 공식적인 파라메타로써 그 argument 값 2가 할당된다. 이 새로운 실행 문맥의 스코프 체인은 ActInner1 -> ActOuter1 -> global object 순서가 된다.

스코프 체인은 표현식 ((arg1 + arg2 ) / (innerArg + localVar)) 의 값을 리턴받기 위해 식별자를 해석한다. 여기서 그 식별자들의 값은 스코프 체인에 있는 객체의 순서대로 객체의 프로퍼티들을 찾아서 해석하고 결정할 것이다.

스코프 체인에 있는 첫번째 객체는 ActInner1 이고, 이 객체는 값이 2인 innerArg 프로퍼티를 가지고 있다. 나머지 3개의 식별자들은 ActOuter1 객체의 프로퍼티에서 찾을수 있다.
즉, arg1 는 2, arg2는 4 그리고 localVar은 8 이다.
그래서 그 함수호출은 ((2+2) / (2+8)) 의 값을 리턴하게 될것이다.

이것을 두번째 전역변수인 secondGlobalVar 변수에 의해 참조되는 함수 secondGlobalVar(5)와 비교해 보자. 새로운 실행문맥 Activation 객체를 "ActInner2" 라고 하면, 이 실행문맥의 스코프 체인은 ActInner2 -> ActOuter2 -> global object 순서가 된다. ActInner2 객체는 값 5를 갖는 innerArg를 리턴하고, ActOuter2 객체는 arg1,arg2 그리고 localVar를 각각 12,3 그리고 8의 값으로 리턴을 하므로, 그 값은 ((12+3) / (5+8)) 이 될것이다.

secondGlobalVar 을 다시 호출하면, 새로운 Activation 객체가 새로 생성된 실행문맥안의 스코프 영역 맨 앞에 추가 될것이다. 하지만 ActOuter2 는 여전히 새로 생성된 실행문맥안에서도 유효하게 되어, Activation 객체 다음에 ActOuter2 객체가 오게 되고, 역시 arg1, arg2 그리고 localVar의 값을 ActOuter2 객체에서 리턴받게 될것이다.

이것이 바로 ECMAScript 에서 중첩 함수를 가지고, 유지하면서 공식적인 파라메타로써 접근 가능하고 선언된 내부 함수와 지역변수들이 어떻게 사용되는지를 보여주는것이다.
각각의 스코프 체인은 내부 중첩 함수가 생성된 실행 문맥 안에서 새로이 추가적인 Activation 객체를 생성하고 사용된다. ECMAScript 스펙에는 스코프 체인은 유한하다고 정의하고 있다. 하지만 그 스코프의 길이는 제약이 없다. 이것을 구현할때는 아마 약간의 실용적인 제약이 부과 되어있을텐데, 현재까지는 아무런 크고작은 문제들이 보고되지 않았다. 중첩함수를 품는 다는 것은 잠재적으로 그 의도를 가지고 있다고 봐야할 것이다. (역주 : 머 중첩함수를 함수로 가지지 말라는 의미같다. 중첩함수를 가진다면, 분명히 그 명확한 의도를 가지고 코딩을 해야한다는 의미??)

클로져를 가지고 무엇을 할수 있을까?

이상하게 들릴찌도 모르겠지만, 그 답은 어떤것도 그리고 무엇이든 할수 있다. 내가 예기하고자 하는건 클로져는 ECMAScript에서 어떤것을 모방하기 위해 허용했다는 것이다.
그래서 그 한계는 무엇을 만들고자 하는가에 따라 다르고, 구현도 마찬가지다. 약간 난해하긴 하지만 아마도 실질적으로 보다 나은 먼가를 해볼수 잇을것이다.

Example1 : setTimeout with Function References

클로져의 일반적인 사용은 함수를 실행하기에 앞서 그 함수를 실행하기 위한 파라메타로서 제공하는 것이다. 예로들면, 일반적인 웹 브라우져 환경에서 setTimeout 함수의 첫번째 인자로써 클로져를 제공할수 있다는 얘기다.

setTimeout 은 첫번째 인자로 넘어온 함수의 실행을 조절하고 두번째 인자로 넘어온 값을 milliseconds로 인식하고 interval 로 사용한다. 만약에 어떤 코드 조각을 setTimeout 에서 사용하기를 원한다. 이것은 setTimeout 함수를 호출하고, 첫번째 인자를 함수객체의 참조로 넘겨준다. 그리고 두번째 인자를 밀리세컨 인터벌로 사용한다. 하지만 함수 객체에 넘겨지는 참조자는  setTimeout 함수의 예정된 실행안에서는 파라메타를 제공할수 없다.

하지만, 코드는 또 다른 함수객체를 호출할수있다. 이 함수 객체는 내부에 중첩된 함수 객체를 참조하는 참조자를 리턴값으로 같는 함수객첵이다. 이 내부의 중첩된 함수 객체는 참조에 의해서 setTimeout 함수에 넘겨진다. 내부함수 실행에 사용되는 파라메타들은 함수 호출의 리턴값으로 넘겨받는다. setTimeout 은 넘겨받는 인자 없이 중첩된 내부함수를 수행하지만, 그 내부함수는 여전히 외부함수에게 호출의 리턴값으로 넘겨주는 파라메타들에 여전히 접근할수 있다.
(역주: 먼소린지... -_- )

function callLater(paramA, paramB, paramC){
    /*  
     함수 표현식을 갖는 익명 내부 함수를 만들고 이것을 참조자로 리턴한다.
    */

    return (function(){
        /* 이 내부 함수는 setTimeout 과 함께 실행 되어진다.
            그리고 이분이 실행되어 질때, 이것은 읽을수 있고, 수행되고,
            파라메타들을 외부 함수에 넘겨준다.
        */

        paramA[paramB] = paramC;
    });
}

...
/*
   함수를 호출한다. 이 함수는 실행문맥안에서 생성된 내부 함수 객체를 가르키는 참조자를 리턴해줄것이다. 넘겨준 파라메타들은 결과적으로 외부함수의 인자로 실행될때, 내부함수가 사용할것이다. 내부 함수객체를 가르키는 리턴된 참조자는 지역변수에 할당된다.
*/

var functRef = callLater(elStyle, "display", "none");
/*
  setTimeout 함수를 호출하고, 첫번째 인자로 할당한 내부 함수를 참조자를 functRef 변수로 넘겨준다.
*/

hideMenu=setTimeout(functRef, 500);
Example 2: Associating Functions with Object Instance Methods (객체 인스턴스의 메소드를 갖는 연관 함수)

함수객체를 가르키는 참조자가 할당되어, 함수의 파라메타로 유용하게 사용되는 경우는 많다.
하지만 실행시간에 할당되기 전까지는 쓸수가 없다.

아래예제는 어떤 특별한 DOM 엘리먼트와의 상호 작용을 하기위해 설계된 자바스크립트 객체를 나타낸다. 이 객체는 doOnClick, doMouseOver, doMouseOut 메소드를 가지고 있고, 이런 메소드들을 DOM 엘리먼트에 상응하는 이벤트들이 발생한다. 하지만 수 많은 자바스크립트 객체 인스턴스들이 서로다른 DOM 엘리먼트로 부터 생성되고, 어떤 객체 인스턴스들은 어떻게 자신들이 코드안에서 쓰여질것인지 알수 없다. 즉, 어떤 전역객체가 어떤 인스턴스의 참조로 쓰일지 알수 없기 때문에 객체 인스턴스들이 어떻게 스스로가 전역적으로 참조가 되는지 알지못한다.

그래서 이 문제는 특정 자바스크립트 객체의 인스턴스를 가지는 이벤트 핸들링 함수를 실행함으로써, 어떤 객체의 메소드가 실행되었는지를 판별해 알아낸다.

아래 예제는 약간은 일반적인 클로져 기반의 함수를 사용한다. 이 함수는 엘리먼트 이벤트 핸들을 가지고 있는 객체 인스턴스를 포함한다. 정리하면, 이벤트 핸들러의 실행은 그 객체 인스턴스의 특정 메소드를 호출하고, 이때 이벤트 객체와 그 객체 메소드와 연관된 엘리먼트의 참조자를 인자로 넘겨주고, 그 메소드의 리턴값을 넘겨받는다.

/* 
이벤트 핸들러와 연관된 객체 인스턴스의 일반적인 함수.

내부 함수는 이벤트 핸들러로써 사용되는 내부함수를 리턴 한다.
객체 인스턴스는 obj 파라메타를 넘겨받는다. 그리고 그 메소드 이름은
객체가 methodName 파라메타를 넘겨줄때 호출된다.
*/
function associateObjWithEvent(obj, methodName){ /*
그 리턴된 내부 함수는 DOM 엘리먼트를 위한 이벤트 핸들러로 사용된다. */
return (function(e){ /* The event object that will have been parsed as the - e - parameter on DOM standard browsers is normalised to the IE event object if it has not been passed as an argument to the event handling inner function:- */ e = e||window.event; /* The event handler calls a method of the object - obj - with the name held in the string - methodName - passing the now normalised event object and a reference to the element to which the event handler has been assigned using the - this - (which works because the inner function is executed as a method of that element because it has been assigned as an event handler):- */ return obj[methodName](e, this); }); } /* This constructor function creates objects that associates themselves with DOM elements whose IDs are passed to the constructor as a string. The object instances want to arrange than when the corresponding element triggers onclick, onmouseover and onmouseout events corresponding methods are called on their object instance. */ function DhtmlObject(elementId){ /* A function is called that retrieves a reference to the DOM element (or null if it cannot be found) with the ID of the required element passed as its argument. The returned value is assigned to the local variable - el -:- */ var el = getElementWithId(elementId); /* The value of - el - is internally type-converted to boolean for the - if - statement so that if it refers to an object the result will be true, and if it is null the result false. So that the following block is only executed if the - el - variable refers to a DOM element:- */ if(el){ /* To assign a function as the element's event handler this object calls the - associateObjWithEvent - function specifying itself (with the - this - keyword) as the object on which a method is to be called and providing the name of the method that is to be called. The - associateObjWithEvent - function will return a reference to an inner function that is assigned to the event handler of the DOM element. That inner function will call the required method on the javascript object when it is executed in response to events:- */ el.onclick = associateObjWithEvent(this, "doOnClick"); el.onmouseover = associateObjWithEvent(this, "doMouseOver"); el.onmouseout = associateObjWithEvent(this, "doMouseOut"); ... } } DhtmlObject.prototype.doOnClick = function(event, element){ ... // doOnClick method body. } DhtmlObject.prototype.doMouseOver = function(event, element){ ... // doMouseOver method body. } DhtmlObject.prototype.doMouseOut = function(event, element){ ... // doMouseOut method body. }
그래서 이 DhtmlObject의 인스턴스는 내부적으로 어떻게 다른 코드에 의해 쓰이고, 전역 네임스페이스와 다른 DHtmlObject 인스턴스와의 충돌 신경쓰지 않고도, 스스로 DOM 엘리먼트를 연관시킬수 있다.

Example 3: Encapsulating Related Functionality

클로져는 서로 의존적이거나 밀접한 관계가 있는 코드를 묶고 우연히 발행하는 상호작용의 위험들을 최소화하는 방법으로 추가적인 스코프를 만들수 있다.

(역주: 아~ 길다... 이번 예제는 그냥 읽어보고, 내가 이해한다로 그냥 쓰겠음..)
2008/04/04 16:32 2008/04/04 16:32

Trackback URL : http://miconblog.com/tc/sohn/trackback/390