RSS구독하기:SUBSCRIBE TO RSS FEED
즐겨찾기추가:ADD FAVORITE
글쓰기:POST
관리자:ADMINISTRATOR
앞절에서 이미 언급한 부분입니다. Scope에 관한거죠. 못읽어 보신분이라면 언능가서 먼저 읽으시길 바랍니다. 이 강좌를 하면서 Scope의 개념이 없이는 절대 안되는 부분이거든요. 물론 깊게 설명하지는 않았습니다만,
이번 강좌를 이해하는데 많은 도움이 되리라 생각합니다.

//중첩함수
function A(){
var a = "1";
  function B(){
    var b = "2";
  }
}


이러한 함수가 정의되어 있다고 가정합시다. 함수 A는 a라는 지역변수를 가지고 있고 함수 B는 함수 A안에서
존재하고 b라는 지역변수도 가지고 있습니다.
깜짝 질문, 함수 B에서 a라는 변수를 참조할 수 있을까요? 없을까요? 앞장에서 했던 강좌를 유심히 보신분
이라면 쉽게 알 수 있을 겁니다. 답은 "참조할 수 있다" 입니다. 답은 앞장의 강좌를 다시 읽어서 확인하시기 바랍니다.

참고로 위의 구문은 "중첩함수"라고 부르며 Javascript에서의 private을 만들때 사용하는 방법중에 하나입니다. 실제로 해보시면 아시겠지만, 함수 B는 함수 A안에서밖에 호출할 수 없으며 밖에서 함수 A를 가지고 함수 B를 참조하려 해도 안되는걸 아실 수 있습니다. A.B() <- 이런식으로 절대 호출 할 수 없습니다.

그럼 다음 함수를 보시죠.

<html>
<script>
 function OutFunc(){
   var out = "i'm a out"
   function InFunc(){
     var inner = "i'm a in "+"-----"+ out;
     return inner;
   }
   return InFunc;
 }

 var test_1 = OutFunc();
 var test_2 = test_1();
 alert(test_2);
</script>
<body></body>
</html>

이 스크립트의 결과가 어찌 될거라고 예상이 됩니까? 사실 이 함수 자체를 이해 못하시는 분도 있으리라
생각합니다. 절대 무시하는게 아닙니다. 다만 우리는 알게모르게 쉬운 스크립트만을 접하고 이렇게 구조적으로 파헤친적이 없기때문에 어려울 수 밖에 없었던겁니다. 처음부터 Java 나 C#처럼 배웠다면 이렇게 Javascript가 우리를 힘들게 하지 않았을거라고 전 생각합니다. 일단 실행부터 시켜보세요. 어떻습니까? 여러분이 예상했던 답하고 일치합니까? 일치하다면 자신있게 설명을 하실 수 있습니까? 없다면 아래를 봐주세요 ^^ㅋ

먼저 정리부터 해보죠.
1. Javascript에서의 단위는 함수이다.
2. 함수의 지역변수는 함수밖에서는 절대 호출 할 수 없고, 함수내부에서는 전역변수보다 지역변수가 우선한다.
3. 변수는 각각의 Scope를 가지며 자신의 Scope를 벗어날 수 없다.


이제 소스를 해석해보겠습니다. 다들 청심환 하나씩 드세요. ^^
OutFunc는 자신의 지역변수 out을 가지고 있고 중첩함수인 InFunc를 가지고 있습니다. InFunc는 자신의
지역변수인 inner를 가지고 있고 중첩된 함수(nested function이라고 표현합니다) InFunc는 지역변수 inner를 리턴하는군요. OutFunc는 중첩함수인 InFunc를 리턴하구요. 여기까지 이해하셨죠?

그리고 OutFunc를 실행하고 test_1에 담습니다..여기엔 뭐가 담길까요? 그렇습니다. 함수 InFunc가 담깁니다. 그리고 test_2에는 test_1에 담긴 함수를 실행하고 그결과를 담습니다.

그럼 답을 발표하겠습니다. 답은 "i'm a in -------- i'm a out" 입니다.

제가 묻겠습니다. 변수 inner는 어디에 속해이어야 하는 거죠? 네. 함수 InFunc에 속해서 그 Scope안에서만
존재 해야하며 그함수가 종료되었을때, 같이 사라져야할 변수입니다. 그런데 지금의 모습은 어떻습니까?
alert()을 통해서 inner의 값을 찍고 있습니다. 도대체 어떻게 된 것일까요? 위에 정리한 1,2,3은 도대체
어쩌라고 이렇게 결과가 나오는 겁니까?

눈치가 빠르신분은 이미 알고 계셨을 겁니다. 그렇습니다. 이것이 바로 "클로져(Closure)" 입니다.
클로져의 정의는 "다른 함수내에서 내부객체로 생성된 함수 리터럴을 반환하여 호출 프로그램에서 이를 변수로 배정한 것" 이라고 정의하고 있으며 부연설명으로는 "함수가 동작하는데 필요한 데이터 영역 확장"이라고 하고 있습니다.[o'relly - Javascript for web 2.0 5장]

제가 더 덧붙여 설명하면 첫번째  OutFunc()를 실행함으로써 InFunc()함수를 OutFunc()의 안에서 바깥쪽으로
끄집어내어 영역을 변경(확장)하고 두번째 test_1()을 다시 한번 실행함으로써 inner의 값을 직접 호출 할 수 있도록 호출 프로그램으로 영역을 변경(확장)한 것입니다. 이로써 inner라는 변수는 더이상 OutFunc의
InFunc안에 갇혀있지 않고 어디서든지 호출 될 수 있는 전역변수화가 되어버린 것이죠.
여기까지 이해하셨다면 당신은 이미 중급의 문턱까지 온것입니다.
사실 클로져란것을 정확하게 알고 있는 사람은 드물뿐 아니라 "의도하지 않은 클로져의 생성"으로 고통받고
있는 개발자들도 많습니다. 그만큼 어렵고 이해하기 힘든 개념인 것입니다.

결과적으로만 보면 상당히 훌륭해보이고 멋져 보이지만, 사실 내면엔 "메모리 누수"라는 문제점을 안고
있습니다. 메모리 누수에 대해서는 다음 강좌인 "함수-3"에서 자세히 다뤄보도록 합시다.
오늘은 이만 접어야 겠네요..ㅋ 퇴근시간이 가까워 져서리..ㅋㅋ
그럼 다음 강좌까지 다들 건강조심세요.. 더위가 심하네요..


오늘은 저번 강좌에 이어 클로져에 대해서 이어가겠습니다. 클로져에 대해서 개념이 안잡히신 분들을 위해
간략하게 정리하겠습니다.

함수안에 존재하는 지역변수는 지역변수로써 살아야하며, 함수가 죽으면 따라죽을 운명인데, 클로져는 죽어
마땅한 함수의 지역변수를 밖으로 끄집어내어 영역을 변경(확장)하는 것
을 의미합니다. 물론 이한줄이 클로져를 전부 나타낼 수는 없습니다. 하지만 전 이렇게 설명하려 합니다. 다르게 설명해봤자 서로 헛깔릴게 분명
하거든요 ^^ 사람이든 변수이든 자기의 영역에서 벗어나면 안되나 봅니다..ㅎㅎ 왜냐구요? 클로져도 문제가
좀 심각하거든요... 이름하여 "Memory leak(메모리 누수)" 현상 때문입니다.

메모리 누수의 전체적 그림은 중급강좌정도에 보실 수 있겠네요. 여기서는 클로져에 의한 메모리 누수에
대해서만 다루겠습니다. 메모리 누수 모델이 몇가지 있는데 그걸 다 다루자면 제목에서 보듯이 [기초강좌]에서 범위가 벗어납니다. 우리가 좀전에 뭘 배웠습니까? 영역을 벗어나면 문제가 된다고 했지요 ㅎㅎㅎㅎ

자 다시 본론으로 가서, 클로져는 한 내부함수의 지역변수를 함수 리터럴을 통하여 밖으로 꺼내오는 것을
의미한다 했습니다. 그럼 제가 질문을 해보겠습니다. 그 한 내부함수가 종료되면 클로져에 의해 밖으로
튀어나온 변수는 어떻게 될까요? 그렇습니다... 계속 살아 있습니다. 계속 살아있으므로 부모함수가 죽었는데도 불구하고 계속 호출되어 사용됩니다. 나중에 다시 이야기 되겠지만 이것이 "IE에서의 메모리 누수 모델 1"
입니다. 보통 FireFox나 오페라 같은 브라우저는 클로져가 생성되어도 참조값이 0이면 브라우저메모리에서
GC에의해 소멸되게 됩니다. 허나, IE는 그렇지 못하여 문제가 되는거죠. 전 강좌를 열심히 보신분이라면 쉽게 이해하시리라 봅니다. 클로저의 메모리 누수는 이쯤으로 마치겠습니다. 사실 메모리 누수에 대해서는 할말이
무지 많습니다. 허나, 역시 범위를 넘기면 문제가 ㅎㅎㅎㅎ



posted by blankus

--------------------- 펌글 ---------------------


음냥 초보자들이 알기 딱좋네 !!

나두 넘 좋아 나도 초보거등 ~~~ ㅋㅋㅋ
2007/11/21 12:54 2007/11/21 12:54
이 글에는 트랙백을 보낼 수 없습니다
오늘도 무쟈게 덥군요 ^^; 좀전에 회사 사람들하고 잠시 밖에나가 잡담을 하며 바람좀 쐬고왔습니다.
역시 사무실에 일할때보단 밖에서 놀때가 더 좋군여..ㅎ

오늘은 By value와 By reference에 대해서 알아보겠습니다. 날도 덥고하니 조금은 짧게 이야기해봅시다.
사실 이부분은 제가 C를 공부할때 알게되었습니다만, Javascript에서는 크게 신경안쓰고 작업했던 부분입니다. C를 공부하신 분은 아시겠지만, C에는 포인터란 개념이 있습니다. 물론 Javascript에서는 없습니다.
하지만 표면적으로 C처럼 포인터를 핸들링 하지 않는것 뿐이지, 개념은 필요한 부분입니다.

포인터.. 그놈은 이런놈입니다. 보통 우리가 변수를 선언하고 무언가를 그 변수에 담을때, 그때 그 무언가는
메모리에 저장되게 됩니다. 메모리에 저장될때, "나는 메모리의 어디에 위치해있다."라고 알려주는데 그게 바로 "포인터" 입니다. 말그대로 무언가를 가리키는거죠. 그래서 C에선 일반변수와 포인터 변수를 구분해서
사용하는데, Javascript에서 사용하는 변수는 C에서의 일반 변수가 되겠습니다. 포인터 변수는 뭔가 저장된놈의
메모리 주소를 가지고 있는거구요. 일반 변수는 by value가 되고, 포인터 변수는 넓은 의미에서 by reference가 됩니다. 물론 Javascript에 100% 적용되는 설명은 아닙니다.
여기까지는 쉽게 이해가 가실거라 생각됩니다. 아직 부족하다구요? 그럼 더 쉽게 --;

by value의 참조는 변수에 값을 직접할당하는 방식이며 (변수를 박스라고 생각하시면) 박스안에 내용물이
중요한 놈이죠.
by reference의 참조는 변수에 값을 직접 할당하지 않고,(변수를 박스라고 생각하시면) 박스의 위치에
관심이 있는 놈입니다.
내용물에 뭐가 담겨져있고, 그 내용물이 어찌되던간에 그 박스의 위치만 잘 가지고 있으면 되는 놈이죠.
나중에 변수를 call하면 by reference의 참조는 "값을 가져오라고? 그래. 난 박스주소를 알고있으니까, 그박스에 뭐가있는지는 잘모르지만 내용물을 넘겨줄께"라고 행동하게 됩니다.

그럼 Javascript에서는 위의 개념을 어떻게 가지고 있을까요? 이제 소스를 보면서 이야기해보죠.

<html>
<script>
   //첫번째 구문
   var ref_1 = "string...";
   var test_1 = ref_1;
   ref_1 = "none";

   //두번째 구문
   var ref_2 = new String("hello world");
   var test_2 = ref_2;
   ref_2 = new String("world");

   //세번째 구문
   var ref_3 = new Array("one", "two", "three");
   var test_3 = ref_3;
   ref_3[0] = "1";

</script>
<body>
</body>
</html>


우선 '첫번째 구문'에 대해서 살펴보죠. 아주 쉽습니다. 단순히 문자열을 변수에 담고있고, 그것을 다시 다른 변수에 담았습니다.
그리고 원래 변수의 값을 변경했군요.

'두번째 구문'은 결과적으로 '첫번째 구문'과 동일하지만 다른점이 하나 있습니다. 바로 new String이란 것을 사용해서 변수에 담고있습니다.
'첫번째 구문'에서 ref_1의 타입은 Stirng이지만, '두번째 구문'에서 ref_2의 타입은 Object입니다. 역시 마지막에선 다른 String Object로
값을 변경하고 있네요.

'세번째 구문'은 많이 사용하는 배열입니다. 배열은 자체가 Object인건 아시죠? 역시 이것도 test_3이라는 변수를 만들고 ref_3을 담았습니다.

이상 소스설명은 드렸습니다. 여기까지 이해가 가시나요? 혹시 안가신다면 제게 질문을 하시거나, 구글링을통해 학습이 필요합니다.
이제 제가 질문을 드리겠습니다.

1. alert(test_1)를 실행하면 값이 어떻게 나올까요?
2. alert(test_2)를 실행하면 값이 어떻게 나올까요?
3. alert(test_3)를 실행하면 값이 어떻게 나올까요?

3개의 답을 모두 자신있게 말씀하셨다면, 거기에 답까지 맞으셨다면 축하합니다 ^^*

1번의 답은 "String..."이 출력됩니다. 이유는 String타입의 값들은 by value에 의해서 참조가 됩니다.
다시말해서, test_1은 ref_1의 주소값을 가지고 있는게 아니라 "String..." 자체의 값 즉, value를 가지고 있는겁니다. 그러므로 아무리 ref_1의 값을 변경해도 값에의한 참조(by value)를 한 test_1은 영향을 받지
않는거죠.

2번의 답은 3번을 설명하고 다시 이야기하겠습니다.
3번의 답은 "1, two, three"가 되겠습니다. 왜일까요? 그렇습니다. 둘중에 하나니까 그렇습니다 --;
위에서 값에의한 참조였으니, 이번엔 그반대의 경우겠지요 ^^; 물론 맞는 답입니다만, 정확한 설명을 드리면, ref_3은 Object를 담고있습니다. 그리고 test_3는 ref_3을 참조하지요. 그리고 Object는 by reference에 의해서 참조가 됩니다. 위에서 제가 말씀드린게 by reference가 되면 값을 가지고 있는게 아니라 참조되는 놈의 주소를 가진다고 말씀드렸습니다. 이해가 되시는지요?
만약 값에 의한 참조가 되었다면 test_3는 값이 변경되기전의 ref_3을 참조했기때문에 test_3는 ref_3의 값의 변동에 상관없이 동일한 값을 가지고 있어야함에도 불구하고, 결과는 변경된 값을 가지고 있습니다. 바로 by value가 아닌 by reference에 의해 참조가 되기 때문이지요. by reference로 참조된 변수는 그안에 값이 어떻게 바뀌든지 상관없이 주소값만을 가지고 있으므로 나중에 주소안에 들어있는 값만을 내뱉어 줍니다.
위에 설명 드렸던 부분입니다.

그럼 2번은 결과가 어떻게 될까요? 참으로 애매한 부분입니다. 답은 "hello world"가 출력됩니다
new String으로 생성했으므로 분명 Object입니다, 그럼 by reference에 의해서 참조가 일어나야 정상이지만
결과적으로 그렇지 못합니다 --;
왜그럴까요? 여기에도 무언가가 있기 때문입니다.

바로 Wrapper Object가 범인입니다. Wrapper를 잘모르시는 분을 위해서 잠깐 설명을 드리겠습니다.
Wrapper는 보통 타입을 변경하기위해 사용합니다. String으로 사용하지만 나중의 변환을 위해 new String으로 생성하여 Object로 만들어 내는것입니다. 물론 Javascript에서는 자동 형변환에 의해 크게 사용되지는
않습니다.
다시 본론으로 돌아가서, Wrapper Object는 Object이지만서도 by value에 의해 작동됩니다.
[O'reilly의 Javascript The Definitive Guide 5/E 3장 참고]
(Javascript에서의 Wrapper Object는 String, Number, Boolen이 있습니다. )
그러므로 예상대로 "world"가 출력되지 않고 "hello world"가 출력되는 것입니다.

오늘은 조금 짧게 써보려고 했는데, 말이 길어졌군요 ㅎㅎ 오늘의 주제는 "참조"였는데 만족할 만한
주제였는지 모르겠습니다.
기초강좌라서 약간은 부담이 되는게 사실입니다 ^^; 최대한  쉽게 풀어서 설명하고 싶은데 생각처럼 그렇게
쉽지만은 않군요ㅋ 포스팅을 하다보니 벌써 저녁먹을 시간이네요.. 오늘도 야근모드인
개발자의 비애ㅋ

자 기초강좌가 언제까지 연재 될지 모르겠지만 가끔은 중급강좌도 쓰도록 하겠습니다.
즐거운 저녁식사 되세요~~

posted by blankus


---------------- 트래백 http://www.blankus.net/trackback/5 ----------

위글을 봐서도 알겠지만 ㅡㅡ 뻔히 아는애기 왜하냐고 하면 ㅡㅡ

답이 없지만 서도 ;; 초보자들이 개념만충 하기엔 딱좋은 글이다.

그래서 퍼왔다 !
2007/11/21 12:51 2007/11/21 12:51
이 글에는 트랙백을 보낼 수 없습니다
http://mckoss.com/jscript/object.htm

위의곳을 참조하여보아라

JS 의 참맛을 알게 될것이니 ....

난 지금 js _ abstruct 를 만드는 중인데 .. 참고할만 하다.
2007/11/20 00:57 2007/11/20 00:57
이 글에는 트랙백을 보낼 수 없습니다
그동안 Prototype 자바스크립트 프레임웍의 사용법을 익히느라 필요이상으로 무리하게 사용해 왔습니다. 간단하게 크로스 브라우징이 보장되며 코드를 아름답게(?) 만들 수 있다는 장점 때문이였는데요. 개발하기 편하여 생산성 높은 환경을 제공하는 반면 원시코드에 비해 9배이상의 시스템 성능저하가 나타나기도 합니다. 아래와 같은 테스트용 루프 함수를 만들고 결과는 동일하면서 모든 브라우저에서 통용되며 상습적으로 사용되는 Prototype함수와 자바스크립트 메서드를 벤치마킹해 보았습니다. 테스트에 사용된 Prototype 프레임웍 라이브러리의 버전은 Scriptaculous1.6.1에 포함된 1.5.0_rc0이며, 브라우저는 파이어폭스2.0b, 시스템사양은 Pentium4, 3.00GHz입니다.

// 타임체크 함수
function timeChecker() {
    var befor, loops, after, tpo, tet, result
    before = new Date()
    loops = 500 //루프 수
    for (var i=0; i < loops; i++) {
        Element.update('element', '내용')// 태스트함수
    }
    after = new Date()
    tpo = Math.round(1000*(after-before)/loops)
    tet = (after-before)/1000
    result = 'Time per operation: '+tpo+', Total excuted time: '+tet; //결과값
    throw(result)
    //return result
}
timeChecker();


1. DOM 엘리먼트 스타일 설정:
프로토 타입에서는 여러가지 형태로 DOM을 주무를수 있게 해 주는데요. 1만회에 걸쳐 객체를  width:'300px', height:'100px'으로 변경하는 테스트입니다. 아래의 결과를 살펴봅시다.

1. document.getElementById('엘리먼트').style.스타일 = '값' // (원시코드, 2라인) -->  1.14초
2. $('엘리먼트').style.스타일 = '값' // (2라인) -->  1.656초
3. Element.setStyle('엘리먼트', {'스타일:값의 배열x2'})  // --> 1.985초 
4. $('엘리먼트').setStyle({'스타일:값의 배열x2'}) // --> 3.047초

동일한 결과값을 가지는 위 4가지 코드는 처리에 필요한 시간이 서로 다릅니다. 4번은 원시코드와 2배이상 차이가 벌어지는 재미있는 결과를 보여줍니다. 반복성이 짙은 경우, 즉 엘리먼트를 실시간으로 업데이트하거나 모션에 사용할 코드는 4번의 형태는 가급적 피하는 것이 좋습니다. 당연히 1.번이나 2번의 형태가 좋겠죠?

2. DOM 엘리먼트 스타일 값 구하기:
DOM의 위치또는 모양을 확인하기 위해 prototype은 getStyle, getDimensions 메서드를 제공하고 있으며, 엘리먼트의 높이 값만을 구하기 위한 getHeight와 같은 메서드도 있습니다.(그나저나 getWidht는 왜 없는걸까요?) 엘리먼트의 높이값(height)을 알기위한 가장 빠른 방법은 무엇인지 살펴 봅시다.

1. document.getElementById('엘리먼트').offsetHeight  // (원시코드) -->  Number 0.391초
2. $('엘리먼트').offsetHeight //Number --> 0.625초
3. Element.getHeight('엘리먼트') //Number --> 0.641초
4. document.getElementById('엘리먼트').getHeight() //Number --> 1.328초
5. $('엘리먼트').getHeight() //Number --> 1.563초
6. Element.getStyle('엘리먼트', '스타일')  //String  --> 2.812초 
7. $('엘리먼트').getStyle('스타일') //String  --> 3.843초

높이 값이 '123px'와 같은 스트링으로 반환된 6, 7번은 'px' 문자열을 없애기 위해 parseInt()하거나 '스트링.substring(0,스트링.length-2)'을 사용해야 합니다. 6, 7번은 쓸일이 없을 것 같은데 왜 집어넣었냐구요? 하지만 아래와 같은 경우(다중 이미지 비율계산)에는 벤치마크 결과와는 정 반대로 6, 7번이 훨씬 빠른 성능을 내기도 합니다.(슬라이드를 움직여 프레임을 비교해보세요.)

// Realtime image Ratio
function setRatio(v) {
    var x,y,h
    var w=Math.round(v)
    $$('array').each(function(e){
        x=Element.getStyle(e, 'width'); y=Element.getStyle(e, 'height')
        h=Math.round(v*y.substring(0,y.length-2)/x.substring(0,x.length-2));
        e.style.width=w+'px'
        e.style.height=h+'px'
        e.style.margin=(w-h)/10+'px'
    })
}


Element.getHeight('엘리먼트')
Width: 75px

Element.getStylet('엘리먼트', 'height')
Width: 75px

3. DOM 엘리먼트.Show/Hide:
1. document.getElementById('엘리먼트').style.display = 'block' // -->  0.547초
2. $('엘리먼트').style.display = 'block' // -->  0.812초
3. Element.show('엘리먼트')  // --> 1.312초
4. $('엘리먼트').show()  // --> 2.609초

또한 가장 많이 사용하는 것 중의 하나인 엘리먼트 Show/Hide를 봅시다. 1만회를 루프로 돌린 결과입니다. 스타일의 결과와 비슷하지요? 역시 원시코드가 가장빠릅니다. 하지만 이것은 결과값이 브라우저에 따라 서로 다르게 나타나기도 합니다. IE에서는 $('element').style.display = 'block', 파이어폭스에서는 Element.show('element')가 잘 돌아간다는 느낌입니다.

4. DOM 엘리먼트.InnerHTML:
1. document.getElementById('엘리먼트').innerHTML // -->  0.672초
2. $('엘리먼트').innerHTML = '내용' // -->  0.766초
3. Element.update('엘리먼트', '내용') // -->  2.39초

이것은 3천회 루프의 결과입니다. 이 또한  원시코드를 사용하는 것이 3배 빠르군요. 눈치 채셨겠지만 "오브젝트.메서드"의 결합과 "메서드(오브젝트)"에도 결과는 같지만 소요시간이 다릅니다. DOM 스타일링에는 메서드(오브젝트)와 같은 형태가 좋습니다. 뭐 결과적으로는 원시코드를 적절히 혼용하면 시스템 퍼포먼스를 크게 끌어올릴 수 있다는 것이지만요.

5. 정수 구하기:
1. Math.round(넘버) // -->  1.453초
2. (넘버).toFixed(0) // -->  3.859초

소수점 이하의 수를 반올림하는 경우 100만회 루프 결과 toFixed(0) 보다 Math.round()가 2배이상 빠릅니다.

6. 브라우저별 색상처리:
좀 다른 얘기지만, getStyle로 색상 값에 따른 이벤트를 처리하는 경우 브라우저 마다 가져오는 색상 값이 틀립니다. 스타일스트(CSS)에 '#f80'으로 설정되었을 경우 브라우저별로 가지고 온 결과는 아래와 같습니다.
오페라 = '#ff8800'
파이어폭스 = 'rgb(255, 136, 0)'
익스플로러 = '#f80'


7. 홀수 짝수 구하기:
홀수나 짝수를 루프에서 처리할 때 'n%2'를 쓰거나 'n&1'을 쓰는 두가지 방법이 있습니다. 이럴땐 'n&1'이 미약하게 나마 약간 더 빠릅니다.
for (var n=0; n < 100;n++) if(n%2==1)$('element').innerHTML = 'test';
for (var n=0; n < 100;n++) if(n&1==1)$('element').innerHTML = 'test';


추가. 문자열비교 : match 보다 test가 빠르고 indexOf가 두배 빠름(50만회)
var testing = 'test test test';
testing.match(/test/); // -->  0.702초
/test/.test(testing); // -->  0.643초
testing .indexOf('test ') != -1; // -->  0.395초


추가. if문과 단순 조건문 속도차이 없음.(500만회)
var boo = true; 
boo = boo? 'true' : 'false'; // -->  0.736초
if(boo){ boo = 'true'; } else { boo = 'false'; } // -->  0.726초


추가. DOM Selecter(500회)
$$('#taglist .row');// -->  1.496초
$$('.row');// -->  tset failed
document.getElementsByClassName('row', 'taglist');// -->  0.193초
document.getElementsByClassName('row');// -->  3.321초

2007/11/09 12:29 2007/11/09 12:29
이 글에는 트랙백을 보낼 수 없습니다
자바스크립트(JavaScript)에서는 다음의 함수들로, HTML 페이지 주소를 인코딩/디코딩합니다.

encodeURI() / decodeURI()
최소한의 문자만 인코딩합니다.
; / ? : @ & = + $ , - _ . ! ~ * ' ( ) #
이런 문자는 인코딩하지 않습니다.
http:// ... 등은 그대로 나옵니다.


encodeURIComponent() / decodeURIComponent()
알파벳과 숫자 Alphanumeric Characters 외의, 대부분의 문자를 모두 인코딩합니다.
http:// ... 가 http%3A%2F%2F 로 됩니다.



escape() / unescape()
예전부터 있던 오래된 함수입니다. encodeURI() 와 encodeURIComponent() 의 중간 정도의 범위로 문자를 인코딩합니다.


encodeURI, encodeURIComponent, escape 함수 사용 예제


<html>

<body>

<script type="text/javascript">
  var s;

  s = encodeURI('http://www.google.co.kr/소 설.html');
  document.write('<p>' + s + '<p>');
  // 출력 결과: http://www.google.co.kr/%EC%86%8C%20%EC%84%A4.html


  s = encodeURIComponent('http://www.google.co.kr/소 설.html');
  document.write('<p>' + s + '<p>');
  // 출력 결과: http%3A%2F%2Fwww.google.co.kr%2F%EC%86%8C%20%EC%84%A4.html


  s = escape('http://www.google.co.kr/소 설.html');
  document.write('<p>' + s + '<p>');
  // 출력 결과: http%3A//www.google.co.kr/%uC18C%20%uC124.html
</script>

</body>
</html>




어떤 함수든 "공백 문자" 즉 스페이스는 %20 으로 치환합니다. 그러나 주소의 공백은 없어야 합니다.


2007/10/23 15:27 2007/10/23 15:27
이 글에는 트랙백을 보낼 수 없습니다

예전부터 궁금했던 cross-domain 의 한계를 극복하는 방법을 찾았습니다. 2005년 자료이지만 근본적으로는 상관 없을듯  합니다. 원문의 내용을 나름 번역을 하였는데 최대한 빠르게 번역을 해보고자 하였기 때문에 어색한 부분이 많습니다. 그런 부분은 원문에 직접 가서 보는게 좋겠습니다.

원문 : http://fettig.net/weblog/2005/11/28/how-to-make-xmlhttprequest-connections-to-another-server-in-your-domain/

Updates to this post

이 기술문서의 업데이트된 버전(firefox 1.5에서 작업한)을 여기에서 볼 수 있다.

The problem

XmlHttpRequest(이하 XHR) 객체는  다른 서버의 데이터 접근으로부터 해당 데이터를 보호하기 위해 브라우저의 same origin security policy으로 둘러싸여져 있다. 이것은 Ajax 개발자에게 심각한 한계에 부딪히게 한다: 당신은 뒷단에서 server에 호출하기 위해 XHR을 사용할수 있을 것이다. 하지만 그것은 현재 페이지와 같은 서버에서 진행되어야만 한다. 이러한 한계점을 해결할수 있는 대안으로 알려진 것으로는 server-side reverse proxyingbypassing XmlHttpRequest entirely이 있다. 나의(원문 필자)의 경우 이러한 접근들 어느쪽도 실무와 연결되지 않았다. 나는 LivePage  (Divmod에서 Donovan Preston 과 다른 훌륭한 해커들에 의해 개발되어진 live-update 프래임워크)를 사용하길 원했다. LivePage는 동시에 많은 장기유지(long-lasting)되는 네트워크 연결을 핸들링하는데 좋은 Twisted를 사용하였기 때문에잘 작동되었다. 많은 long-lasting 연결을 핸들링하는데 아파치가 좋지 않게 됨에 따라 Twisted server의 앞단에 아파치 reverse proxy를 넣는 것은 performance와 scalibility를 손상시키는 주된 요인이될 것이다. 그리고 LivePage가 XHR에 제약을 받게됨에 따라 나는 궁극적으로 non-XHR를 사용할수가 없었다.

JotSpot과 함께 우리의 접근방식은 Twisted server가 모든 페이지의 요청과 XHR 호출을 핸들링하게 하였다. 그것은 JotSpot이 웹사이트 전용으로 standalone일때까지는 좋았다. 그러나 우리의 고객들이 요구하는 것은 보통 xxx.jot.com 사이트에서 Live-style의 실시간 업데이트를 할 수 있는 능력이었다. 그리고 우리는 우리의 도메인에 있는 모든 사이트의 앞단에 Twisted server를 넣는것을 원치 않았다. 따라서 나는 우리의 도에인의 모든 페이지에서도 XHR을 통해 live.jot.com과 통신할수 있게 할 수 있도록 하는 방법을 찾기 시작하였다. 결론적으로, 그것은 가능했다. 하지만 어떤 hoops를 뛰어넘어야만 한다. 여기 있는 예제들에 대해서 두가지를 알아둬라:

  • 나는 실제로 두개의 다른 server를 사용하지 않았다. 나의 예제에서, http://fetting.net/에 있는 페이지는 http://www.fetting.net/.에 XHR호출생성을 시도할 것이다.사실은 나의 아파치가 동일한 가상호스트를 만드는 것이 브라우저에게는 다르지 않게 보인다. 그것들이 같은 hostname을 가지고 있지 않다고 하더라도,그리고 두개의 다른 내용을 가지고 있는 다른 사이트라고 하더라도 다르지않게 취급되어진다. 만약 내가 ajax.fetting.net서버에 그것의 IP주소와 함께 XHR호출을 한다면 결과는 다르지 않다.
  • 나는 getUrl 한수를 제공해주는 간단한 XHR wrapper library를 사용하였다. getUrl은 URL과 callback 함수를 취하고, URL로 XHR연결을 열어준다, 그리고, 결과와 함께 함수를 호출한다. 만약 이것이 흥미롭다면 여기에서 full code를 볼 수 있다.
  • 내가 XHR호출을 만드는 서비스는 간단한 PHP페이지(ajaxdata.php)로 server에서의 편재 UNIX 시간을 출력한다 : 실제로는 그리 유용하지 않지만 테스트를 목적으로 하기에는 충분히 좋다.

첫번째 시도 : 고지식한 접근방식

여기 내가 시도한 첫번째 방식이 있다 :

다른 subdomain의 페이지의 full URL과 함께하는 XHR. 나는 이것은 동작하지 않을 것이라 확신했지만, 나는 이것을 검증해보고 싶었다 :

  1. <html>
    1. <head>
      1. <script type=”text/javascript” src=”xmlhttp.js”></script>
      2. <script type=”text/javascript”>
        1. var AJAX_URL=”http://www.fettig.net/playground/ajax-subdomain/ajaxdata.php”;
        2. function getTime(){
          1. getUrl(AJAX_URL, gotTime);
        3. }
        4. function gotTime(status, headers, result) {
          1. document.getElementById(’time’).innerHTML = result;
          2. setTimeout(getTime, 1000)
          3. }
        5. window.onload = getTime;
      3. </script>
    2. </head>
    3. <body>
    4. <div id=”time”>
    5. </div>
    6. </body>
  2. </html>
이 페이지는 fetting.net으로부터 전달되어졌고, www.fetting.net으로 XHR 호출을 생성하려하고 있다. 실제로는, 이러한 것들이 같은 server겠지만, browser는 그것을 알지 못한다. 놀랄것도 없이, 이것은 어떤 브라우저에서도 동작하지 않는다. 당신은 다음과 같은 security error를 받을 것이다(firefox로 부터) :
  1. uncaught exception : Permission denied to call method XMLHttpRequest.open
당신은 여기서 그것을 해볼 수 있다.

두번째 시도 : iframe과 document.domain 사용하기

나의 최근 JotSpot 사무실로의 방문에서 Alex는  iframe과 document.domain 프로퍼티를 가르쳐주었다. iframe은 다른 페이지로부터 현재의 페이지에 데이터가 로드되는 것은 XHR과 유사하다. 그러나 iframe은 그것들이 포함하고 있는 페이지들과 동일한 웹서버로부터 당겨오는 페이지에 한계가 없다 - 그들은 그 어떤 URL도 로드할 수 있다. cross-site 보안문제를 예방하기 위해서 브라우저는 javascript object model에 same origin policy를 강요한다:한 frame에서 동작하는 script는 각각의 페이지가 동일한 서버에서 온 것이라 할 지라도, 다른 어떤 iframe 안에 있는 객체에도 접근을 할 수 없다. 그러나, 이러한 규칙에도 예외가 있다. 만약 각각의 페이지가 동일한 parent domain으로 부터 왔고, 각각의 document.domain프로퍼티에 동일한 parent domain을 가리키도록 설정하면, 각각의  iframe에서 동작하나는 script는 각각 서로간에 통신을 허용할 것이다.

예를 들어, http://www.example.com/http://ajax.example.com/을 iframe에 로드한다고 하자. 각각의 페이지가 example.com 도메인에 있는 한, 만약 각각의 document.domain이 "example.com"으로 설정되어 있다면 그것들은 서로간의 데이터에 접근을할 수 있게된다.

그럼, 당신은 iframe과 document.domain을 XHR 연결에 사용할 수 있는 것인가? 두가지의 제약사항과 함께 가능하다.

  1. iframe은 반드시 당신이 XHR호출을 하는 서버로부터 와야 한다.
  2. 반드시 document.domain을 설정하기 전에 XHR 연결(open)을 해야만 한다.

여기 내가 사용한 코드가 있다. 첫번째로, test2.html :

  1. <html>
    1. <head>
      1. <script type=”text/javascript”>
        1. document.domain=”fettig.net”;
        2. function gotTime(result) {
          1. document.getElementById(’time’).innerHTML = “Server timestamp: ” + result;
        3. }
      2. </script>
    2. </head>
    3. <body>  
      1. Single XmlHttpRequest. Works in all modern browsers.
      2. <div id=”time”></div>
      3. <iframe src=”http://www.fettig.net/playground/ajax-subdomain/test2-iframe.html”>    </iframe>
    4. </body>
  2. </html>
이 페이지의 script는  document.domain을 설정하고, XHR 호출의 결과를 핸들링하는 gotTime 함수를 정의한다. 모든 XHR 덩어리들은 iframe에서 일어난다. 여기에 test2-iframe.html코드가 있다 :
  1. <html>
    1. <head>
      1. <script type=”text/javascript” src=”xmlhttp.js”></script>
      2. <script type=”text/javascript”>  
        1. var AJAX_URL=”http://www.fettig.net/playground/ajax-subdomain/ajaxdata.php”;    function gotResult(status, headers, result) {
          1. document.domain = “fettig.net”; // set d.d before talking to parent frame      window.parent.gotTime(result);
        2. }
        3. getUrl(AJAX_URL, gotResult);
      3. </script>
    2. </head>
  2. </html>

test2-iframe.html은 www.fetting.net에 XHR호출을 만든다. 그것은 www.fetting.net으로 부터 왔기때문에 가능한 것이다. 따라서 그것은 단지 그것의 원천 서버에 요청을 하것 뿐이다. 그것이 한번 응답을 받으면, 그것은 parent frame의 document.domain과 매칭시키기 위하여  document.domain을 fetting.net으로 설정한다.이제 그것은 javascript를 사용하여 parent frame에 요청을할 수 있다. 따라서 그것은 window.parent.gotTime을 하는것이 가능하다.

이것을 하기 위한 중점은 올바른 요청에 있다. 당신이 한번 document.domain을 설정하면 당신은 XHR 호출을 만들 수 잇는 능력을 잃게 된다. 따라서 당신은 document.domain을 설정하고 parent frame과 통신을 가능케 하기 전에 당신의 XHR 작업에 신경을 써야할 것이다.

Konq이러한 규칙을 따르면 잘 작동할 것이다. 이 기술들은 현대의 모든 브라우저에 잘 작동되어진다(나는 IE6, Firefox1.0.7, Safari 1.3, Opera8.5, 그리고ueror 3.4에서 테스트를 해보았다)
당신은 여기에서 그것들은 테스트해볼수 있다.

세번째 시도 :  XmlHttpRequest 반복하기

나는 우리의 도메인에 있는 다른 서버로 한개의 XHR을 생성하는 방법을 해결하여 기뻤다.  그러나 현시점에서 이 기술은 거의 심각한 한계를 가지고 있다.:당신은 XHR호출을 당신이 document.domain을 설정한 시점에서만 할 수 있다. 당신이 한번 그렇게 하면, 당신은 parent frame과 통신할 수 있는 능력을 얻게 되지만, 앞으로 XHR 호출을 할수있는 능력은 잃게 된다. 계속적으로 서버에 XHR 호출을 생성하고 결과를 핸들링해야하는 능력이 필요한 LivePage에게는 이것은 좋지 못한 방법이었다. 나는 iframe이 server(XHR를 사용하여)와 parent frame(document.domain을 설정해야지만 할 수 있는)과의 통신 모두를 할 수 있는 방법이 필요했다.

이로서 질문한가지를 떠오르게 하였다 : document.domain 프로퍼티를 한번만 설정할수 있는가? 혹은 fly에서 그것을 switch할 수 있는가? 만약 그 변경을 앞으로 혹은 뒤로 되돌릴수 있다면, 그것을 앞으로 뒤로 바꾸는 것은 가능할 것이다. 여기에 iframe의 수정된 버전인 test3-iframe.html이 있다.

  1. <html>
    1. <head>
      1. <script type=”text/javascript” src=”xmlhttp.js”></script>
      2. <script type=”text/javascript”>  
        1. var AJAX_URL=”http://www.fettig.net/playground/ajax-subdomain/ajaxdata.php”;
        2. function gotResult(status, headers, result) {
          1. var oldDomain = document.domain;
          2. document.domain = “fettig.net”;
          3. window.parent.gotTime(result);
          4. document.domain = oldDomain;
          5. setTimeout(getTime, 1000);
        3. }
        4. function getTime(){    
          1. getUrl(AJAX_URL, gotResult);
        5. }
        6. getTime();
        7. </script>
      3. </head>
    2. </html>
결과는 test2-iframe.html에서와 비슷하게, test3-iframe.html에 있는 gotResult 함수는 document.domain을 fetting.net(parent frame에 접근을 가능케 하기 위해)로 설정하고 window.parent.gotTime을 호출한다. 그러나 첫번째로 해당 페이지가 전송되어진 호스트를 디폴트로 하는 현재의 document.domain값을 저장해놓는다. 그것은 parent와의 작접을 끝낸 후, 그것은 document.domain을 원래의 값(XHR 을 생성할 수 있는 처음에 저장한 domain주소)으로 바꾸어 설정한다.  그런다음 그것은 다른 XHR 호출을 설정하기 위하여 setTimeout을 사용한다. 결과는 main page에서 timestamp가 계속해서 갱신되어져야만 한다.
당신은이 동작을 여기서볼수 있다.
이 페이지는 iframe이 각 시간마다 parent page에 있는 함수를 호출하고 XHR을 반복적으로 생성해내고 서버로부터 결과를 얻어낸다 . 불행하게도 이 기술은 IE,Safari,Knqueror에서는 잘 작동되지만 Mozilla와 Opera에서는 작동하지 않는다. 첫번째 XHR은 동작을 하지만, document.domain을 원래의 값으로 바꾸려고 할때 오류가 발생할 것이다. Firefox에서는 다음과 같은 error message를 발생시킨다.
  1. Error: [Exception... "Illegal document.domain value"   code: “1009″  nsresult: “0×805303f1 (NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN)”   location: “http://www.fettig.net/playground/ajax-subdomain/test3-iframe.html  Line: 13″]
Mozilla와 Opera는 document.domain의 값에 대해서 더욱 엄격하다 - 그것은 오직 현재의 값이나 더 상위 레벨의 도메인으로만 설정이 가능하다.예를 들어서 만약 호스트가 aaa.bbb.example.com이라면, 나는 document.domain을 bbb.example.com으로 바꿀수 있다. 이러한 점에서 나는 그것을 다시 example.com으로 바꿀수 있었지만, 다시 aaa.bbb.example.com으로 되돌릴수는 없었다. 한번 당신이 상위레벨의 도메인으로 옮겼다면 다시는 하위레벨로 수정할수가 없어서 곤경에 빠질 것이다.

네번째 시도 : Mozilla에서 동작하게 하기

나는 Opera없이 살 수 있지만 Mozilla없이는 살 수 없다. 따라서 나는 그 문제를 해결할 수 있는 방법을 찾아내야 했다. 잠시동안 그것에 대해서 생각해본뒤 나는 의문이 생기기 시작하였다 : frame들간에 통신에 있어서 보안상의 제한이 단지 어떻게 엄격하게 규정되어지는가? 우리는 브라우저들이 해당 frame의 document.domain이 같은 값을 가지고 있지 않은 이상은 다른 프레임으로의 접근을 하지 못하도록 한다는 것을 알고 있다. 따라서 child frame들의 document.domain을 설정이 parent의 그것과 같지 않다면 child에서 window.parent.foo에 접근하는 것은 불가능 하다. 그러나 만약 당신이 parent frame에 있는 함수를 지시할수 있도록 childframe에 있는 attribute를 설정할 수 있다면, 그리고 document.domain을 바꿀 수 있다면 어떤가? child frame은 여전히 함수를 호출할수 있을까? 대답은 "그렇다"이다. 그리고 그것은 내가 Mozilla에서 작업하는데 있어서 필요로 하는 것을 충족시켜주었따. 방법은 두개의 frame을 사용하는 것:

  1. parent window  bridge iframe  child iframe
bridge iframe과 child iframe은 모두 당신이 XHR 호출을 만들고자 하는 서버로부터 존성되어졌다. parent apge 코드는 위에서의 예제와 동일하다 :그것은 XHR의 결과를 핸들링하는 함수를 정의하고, iframe을 로딩한다. 여기서는 bridge code를 보자 :
  1. <html>
    1. <head>
      1. <script type=”text/javascript” src=”xmlhttp.js”></script>
      2. <script type=”text/javascript”>  
        1. function gotTime(result) {    
          1. window.parent.gotTime(result); // pass result up to the parent  
        2. }  
        3. window.onload = function(){
          1. var subframe = document.createElement(’iframe’);      document.body.appendChild(subframe);
          2. subframe.src = “test4-iframe.html”;
          3. subframe.contentWindow.bridgeGotTime = gotTime;
          4. document.domain = “fettig.net”;
        4. }
      3. </script>
    2. </head>
    3. <body>
    4. </body>
  2. </html>

이 frame이 로드될때, 그것은 다른 frame 그 안에 로드한다. child frame이 bridge frame과 동일한 서버로부터 로딩되어지는 한, bridge frame은 script를 통해서 child frame의 객체에 접근할 수 있다.bridge frame은 그것만의 gotTime에 subframe.contentWindow.bridgeGotTime를 설정한다. 다음으로 bridge frame은 그것의 document.domain을 그것의 parent widnow의 그것과 매칭시켜 변경한다.이 시점에서 bridge frame은 그들의 document.domain 프로퍼티가 매칭되지 않을때까지 child frame에 직접적으로 통신할수 있는 능력을 잃게 된다. 그러나 child frame은 처음에 설정해놓았던 bridgeGotTime 함수를 통해서 bridge frame과 통신을할수 있는 능력을 유지할수 있게된다. 그리고 bridge frame의 document.domain이 parent의 document.domain과 매칭되는한, bridge와 parent는 자유롭게 통신할수 있다. 나의 제한적인 테스팅에서는,이 기술은 오직 Mozilla-based 브라우저에서만 작동하는것처럼 보였다. (나는 Opera에 대한 방법을 찾지 못했지만, 여기에 시간을 많이 소비할 생각은 없다).

나의 마지막 예제에서는 document.domain을 switching하고, 만약 당신이 document.domain을 재설정하려고 할때 브라우저가 에러를 발생시킨다면 bridge-iframe에 의지할 수 있는 hybrid적인 접근방식을 사용하였다. 여기 main page code가 있다 :

  1. <html>
    1. <head>
      1. <script type=”text/javascript”>
        1. document.domain=”fettig.net”;
        2. function gotTime(result) {
          1. document.getElementById(’time’).innerHTML = “Server timestamp: ” + result;
        3. }
      2. </script>
    2. </head>
    3. <body>
      1. <div id=”time”></div>
      2. <iframe src=”http://www.fettig.net/playground/ajax-subdomain/test4-iframe.html”></iframe>
    4. </body>
  2. </html>
여기까지는 iframe이  test4-iframe.html으로부터 로드되어져 온다는 것을 제외하고는 모든것이 동일하다. 여기 그 페이지의 코드가 있다 :
  1. <html>
    1. <head>
      1. <script type=”text/javascript” src=”xmlhttp.js”></script>
      2. <script type=”text/javascript”>  
        1. var AJAX_URL=”http://www.fettig.net/playground/ajax-subdomain/ajaxdata.php”;    function getTime(){
          1. getUrl(AJAX_URL, gotTime);
        2. }
        3. function gotTime(status, headers, result) {
          1. var oldDomain = document.domain;
            1. if (window.bridgeGotTime) {
              1. window.bridgeGotTime(result);    
            2. } else {
              1. document.domain = “fettig.net”;      
              2. window.parent.gotTime(result);    
            3. }
            4. try {
              1. document.domain = oldDomain;
              2. setTimeout(getTime, 1000);    
            5. } catch(e) {
              1. // denied access to switching the domain, use bridge instead        document.location.replace(”test4-bridge.html”);    
            6. }  
          2. }  
          3. getTime();
          4. </script>
        4. </head>
      3. </html>

이 코드는 XHR서버와 parent frame 각각에 대하여 통신할 필요가 있을때마다 document.domain을 전 후로 변환하는 것을 시도한다. 그러나, 만약에 이것이 실패한다면  그것은 대신하여 bridge iframe을 로드할 것이다. 두개의 다른 페이지가 동일한 일을 하게 하기 위하여,이 페이지는 bridge frame의 child로서도 수행되어진다. 만약 그것이 widnow.bridgeGotTime 어트리뷰트를 보게 되면 그것은 bridge iframe의 아래에서 수행되어진다는 것을알수 있을 것이고, 따라서 그것은 직접적으로 widnow.parent를 요청하는 대신에 window.bridgeGotTime를 요청할 것이다.

당신은 이 액션을 test4.html에서 볼 수 있다. 그것은 Opera를 제외한 최근 모든 브라우저에서 작동한다.

Update : test5.html를 사용하라(갱신되었음). 자세한 내용은 여기 참고

결론 :  당신의 도메인에서 다른 서버에 XHR호출을 만드는 것은 가능하다. 만약 당신이 약간의 설정상의 오버로드를 감안할 의지가 있다면 당신은 반복적으로 호출할 수도 있다. 이것은 여러가지의 이점을 가지고 있다 :
  1. 만약 페이지의 주요 컨텐츠가 아파치나 다른 어떤 웹서버로부터 불러져온다면 그동안 LivePage 요청은을 핸들링하는 전용 Twisted server 를 가질 수 있다.
  2. XHR을 포함하는 웹서비스를 핸들링할수 있는 전용 서버를 가질 수 있다.
  3. two-max-connection이라는 브라우저의 한계로부터 DNS에서 자유롭게 XHR를 사용할 수 있다. 브라우저는 당신이  한번에 두개가 넘은 연결을 할수 없게할 것이다-만약 당신이 JotSpot Live와같은 어플리케이션을 사용하고 한번에 한개가 넘는 탭으로 이동하려고 한다면 뒤죽박죽이 되어질 수 있다. 그러나 만약 XHR이 같은 서버에서 호출되지 않아도 된다면, 당신은 wildcard DNS를 사용할 수 있고,
2007/10/09 13:51 2007/10/09 13:51
이 글에는 트랙백을 보낼 수 없습니다

Ajax에서 서버와의 통신을 위한 핵심 요소인 XMLHttp는

IE7과 모질라 계열 브라우저에서는 native XMLHttpRequest를 사용.
그리고 IE6 이하 버전에서는 Microsoft.XMLHttp를 ActiveXObject로 object 생성.

[ IE에서 생성할 수 있는 XMLHttp object ]
  - Microsoft.XMLHttp
  - MSXML2.XMLHttp
  - MSXML2.XMLHttp.3.0
  - MSXML2.XMLHttp.4.0
  - MSXML2.XMLHttp.5.0


[ Cross-Browser 를 위한 XMLHttp object 메소드 생성 예 ]

function CreateXMLHttp()
{
  if (window.XMLHttpRequest)
  {
    return new XMLHttpRequest();
  }
  else if (window.ActiveXObject)
  {
    var aVersions = [ "MSXML2.XMLHttp.5.0"
      ,"MSXML2.XMLHttp.4.0"
      ,"MSXML2.XMLHttp.3.0"
      ,"MSXML2.XMLHttp"
      ,"Microsoft.XMLHttp"
      ];

    for (var i = 0; i < aVersions.length; i++)
    {
      try
      {
        var oXmlHttp = new ActiveXObject(aVersions[i]);
        return oXmlHttp;
      }
      catch (oError)
      {    
      }
    }
  }
}


[XMLHttp의 메소드 , 프로퍼티 및 이벤트]
1. 메소드
- abort( ) : 요청을 취소
- getAllResponseHeaders( ) : 모든 응답 헤더를 수신
- getResponseHeader(header) : 특정 응답 헤더 수신
- open(RequestType, url, async) : 통신 연결(nativ XMLHttpRequest는 Cross-domain을 제한함.)
   - RequestType : Get or Post

   - url : 서버 주소
   - async : true(비동기 방식 통신), false(동기 방식)
  
- send(content) : 서버로 요청을 보냄. content는 null 을 포함함.
- setHeader(header, value) : 헤더의 키와 값의 쌍을 설정

2. 프로퍼티 / 이벤트 핸들러
- onreadystatechange : readyState가 변경 시 실행될 함수 등록
- readyState : 요청 및 처리 상태
   - 0 (Uninitialized) : 아직 open() 메소드를 호출 하지 않은 상태
   - 1 (Loading) : open() 메소드를 호출한 상태(아직 send는 하지 않은 시점)
   - 2 (Loaded) : 요청을 서보로 보낸 상태( send() )
   - 3 (Interactive) : 일부만 응답 받은 상태
   - 4 (Complete): 모든 데이터를 받고 연결이 끊어진 상태

- responseText : 서버로 부터 받은 결과값의 스트링 반환
- responseXML : 서버로 부터 받은 결과값의 XML 형식
- status  : 서버로 부터 응답 받은 상태 코드 (200 (OK) or 404 (Not Found) 등)
- statusText : status 코드에 대한 의미 명기


[ 서버 전송 예 ]
function sendRequest()
{
    var content = getRequestBody();

    var oXmlHttp = createXMLHttp();
    oXmlHttp.open("post", "http://www.text.com/testForm.aspx", true);
    oXmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

    //서버로 호출이 성공 하였을 경우 처리할 CallBack 함수 등록
    oXmlHttp.onreadystatechange = function ()
    {
        if (oXmlHttp.readyState == 4)
        {
            if (oXmlHttp.status == 200)
            {
                saveResult(oXmlHttp.responseText);
            }
            else
            {
                saveResult("An error occurred: "+ oXmlHttp.statusText);
            }
        }
    };
    
    oXmlHttp.send(content);
}


----------------------- 에이작스 한글깨짐 -----------------------------

JavaScript --> PHP

encodeURIComponent( string ) --> iconv( "UTF-8", "CP949", rawurldecode($string ) )


PHP --> JavaScript

rawurlencode( iconv( "CP949", "UTF-8", $string ) ) --> decodeURIComponent( string )


JSP/Servlet 에서 Ajax와 한글 인코딩 문제


Ajax는 기본적으로 UTF-8 만으로 통신을 한다고 보면된다. 그 이외의 Encoding을 지원하는 인코딩 함수가 없기 때문에 EUC-KR로 데이터를 전송하려는 꿈은 접어야만 한다.
헌데, 아직 우리나라는 EUC-KR 인코딩으로 된 웹 어플리케이션이 압도적으로 많은 상황이다(어서 빨리 UTF-8로 옮겨가길 바라마지 않는다).
거기다가 보통 JSP/Servlet 웹 어플리케이션은 Servlet 스펙 2.3이후부터 문자 인코딩 서블릿 필터를 사용해 모든 요청에 대해 일관된 문자 인코딩을 사용하는 것이 보편적인 방법으로 자리잡았다.

서블릿 필터는 일관성있게 모든 요청을 EUC-KR로 받아들이게 했는데, 몇몇 Ajax관련 요청만 UTF-8로 받아들여야만 하는 것이다.
필터를 적용할 URL-Pattern을 따로 줘보려 했으나, 너무 복잡해졌다.
그래서 HTTP 요청의 헤더를 이용해서 해결 했다.

아.. 한가지 더. 현재 한글 문제는 "XMLHttpRequest 요청 -> JSP/Servlet" 이 상황에서만 발생하는 것이다.
"JSP/Servlet -> XMLHttpRequest"의 상황(서버에서 클라이언트로 값을 리턴)에서는 이 문제가 발생하지 않는다.
서버가 리턴하는 문자열은 간단하게 다음처럼 하면 WAS가 자동으로 UTF-8로 값을 변경해서 전달하기 때문이다.

<%@ page contentType="text/plain; charset=utf-8" pageEncoding="EUC-KR"%>


contentType에서 text/plain은 텍스트나 JSON으로 값을 리턴할 때이다. XML로 리턴할 때는 text/xml.

아래는 Ajax 요청을 처리하기 위해서 만들어본 간단한 Encoding Filter 이다.

package ajax.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 어플리케이션 전체에 적용되는 필터이다.
 *
 * <ul>
 * <li>encoding 파라미터 : encoding 파라미터를 설정하면 request 객체에
 * setCharacterEncoding(encoding)을 실행한다.</li>
 * <li>ajaxFlag 파라미터 : Ajax요청임을 나타내는 HTTP 파라미터 이름이다. ajaxFilter로 지정한 HTTP 파라미터의
 * 값이 true 로 설정되면 인코딩을 무조건 UTF-8로 설정한다.</li>
 * </ul>
 *
 * @author 손권남(kwon37xi@yahoo.co.kr)
 *
 */
public class EncodingFilter implements Filter {

    private Log log = LogFactory.getLog(this.getClass());

    /** HTTP 요청 문자 인코딩 */
    private String encoding = null;

    /** Ajax 요청임을 나타내는 플래그 파라미터 이름 */
    private String ajaxFlag = null;

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        if (ajaxFlag != null
                && "true".equals(((HttpServletRequest) request)
                        .getHeader(ajaxFlag))) {
            // Ajax 처리 요청일 경우 무조건 UTF-8 지정.
            request.setCharacterEncoding("UTF-8");
            if (log.isDebugEnabled()) {
                log.debug("요청 헤더에 " + ajaxFlag + "가 "
                        + ((HttpServletRequest) request).getHeader(ajaxFlag)
                        + "로 설정되어 있어 문자 인코딩에  UTF-8을 사용합니다.");
            }
        } else if (encoding != null) {
            // Ajax 플래그가 true가 아니면, 기본적인 인코딩을 적용한다.
            request.setCharacterEncoding(encoding);
            if (log.isDebugEnabled()) {
                log.debug("문자 인코딩에 " + encoding + "을 사용합니다.");
            }
        }

        chain.doFilter(request, response);
    }

    public void init(FilterConfig config) throws ServletException {
        encoding = config.getInitParameter("encoding");

        ajaxFlag = config.getInitParameter("ajaxFlag");

        if (log.isDebugEnabled()) {
            log.info("encoding : " + encoding + ", ajaxFlag : " + ajaxFlag);
        }
    }

    public void destroy() {
    }
}

이 필터를 적용하고서, web.xml에 다음 내용을 추가하면 필터가 작동한다.
<filter>
    <description>이중 인코딩 필터</description>
    <filter-name>EncodingFilter</filter-name>
    <filter-class>ajax.filter.EncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>euc-kr</param-value>
    </init-param>
    <init-param>
        <param-name>ajaxFlag</param-name>
        <param-value>Ajax</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>EncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

여 기 내용을 보면, 기본적인 인코딩은 EUC-KR이고, 요청 헤더에 "Ajax" 헤더의 값이 "true"일 경우에는 강제로 UTF-8을 지정하라고 한 것이다. "ajaxFlag"의 값을 변경하면 헤더의 키을 "Ajax"가 아닌 다른 값으로도 지정할 수 있다. 하지만 아무튼 해당 헤더의 값을 "true"로 지정하면 Ajax로 인식하게 되는 것이다.

이를 위해서는 XMLHttpRequest에도 한가지 처리를 더 보태야 한다.
2007/07/31 11:50 2007/07/31 11:50
이 글에는 트랙백을 보낼 수 없습니다

3. XML Parser - rssParser.js

 

먼저의 post에서 var xml = rssParser(req.responseXML); 를 보셨을 것입니다.
req.responseXML
을 받아서 JSON으로 파싱 하는 것입니다.
이부분은 rssParser.js javascript파일로 따로 구현해 보았습니다.

 

 

먼저 전체 소스를 보시죠.

 

  0: /**

  1:  *  @(#)rssParser.js    V0.1    2007/03/15

  2:  *

  3:  *  rss XML Parser extend Prototype.js v1.5

  4:  *  Copyright 2005-2007 by VRICKS, All Right Reserved.

  5:  *  http://www.vricks.com

  6:  *

  7:  *  GNU LESSER GENERAL PUBLIC LICENSE

  8:  *  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  9:  *

 10:  *  @Author Woo-Chang Yang, routine@vrick.com

 11:  */

 12:

 13: function rssParser(xml) {

 14:     var v = Try.these(

 15:         // Rss ¹öÀüÀ» °¡Á® ¿É´Ï´Ù.

 16:         function() {

 17:             return xml.getElementsByTagName("rss")[0].getAttribute("version") ? "2.0" : false;

 18:         },

 19:         function() {

 20:             return xml.getElementsByTagName("rdf:RDF")[0].getAttribute("xmlns") ? "1.0" : false;

 21:         },

 22:         function() {

 23:             return xml.getElementsByTagName("feed")[0].getAttribute("xmlns") ? "atom" : false;

 24:         }

 25:     )

 26:     switch(v) {

 27:         case "2.0"  : return new rssParser2(xml); break;

 28:         case "1.0"  : return new rssParser1(xml); break;

 29:         case "atom" : return new rssParserAtom(xml); break;

 30:         default     : return false;

 31:     }

 32: };

 33:

 34: // Rss 2.0 Calss

 35: var rssParser2 = Class.create();

 36: Object.extend(rssParser2.prototype, {

 37:     initialize    : function(xml) {

 38:         var channel = xml.getElementsByTagName("channel")[0];

 39:         this.title  = channel.getElementsByTagName("title")[0].firstChild.nodeValue;

 40:         this.link   = channel.getElementsByTagName("link")[0].firstChild.nodeValue;

 41:         if(channel.getElementsByTagName("image")[0]) {

 42:             var images   = channel.getElementsByTagName("image")[0];

 43:             this.image   = {

 44:                 "url"    : images.getElementsByTagName("url")[0].firstChild.nodeValue,

 45:                 "title"  : images.getElementsByTagName("title")[0].firstChild.nodeValue,

 46:                 "link"   : images.getElementsByTagName("link")[0].firstChild.nodeValue

 47:             };

 48:         }

 49:         else {

 50:             this.image = {

 51:                 "url"    : "",

 52:                 "title"  : "",

 53:                 "link"   : ""

 54:             }

 55:         }

 56:         this.description = Try.these (

 57:             function() {

 58:                 return channel.getElementsByTagName("description")[0].firstChild.nodeValue;

 59:             },

 60:             function() { return "" }

 61:         );

 62:         this.language    = Try.these (

 63:             function() {

 64:                 return channel.getElementsByTagName("language")[0].firstChild.nodeValue;

 65:             },

 66:             function() {

 67:                 return channel.getElementsByTagName("dc:language")[0].firstChild.nodeValue;

 68:             },

 69:             function() { return ""}

 70:         );

 71:         this.pubDate     = Try.these(

 72:             function() {

 73:                 return channel.getElementsByTagName("pubDate")[0].firstChild.nodeValue;

 74:             },

 75:             function() {

 76:                 return channel.getElementsByTagName("lastBuildDate")[0].firstChild.nodeValue;

 77:             },

 78:             function() { return ""; }

 79:         );

 80:         var items = new Array();

 81:         $A(channel.getElementsByTagName("item")).each(function(i){

 82:             items.push({

 83:                 "category" : Try.these(

 84:                     function() {

 85:                         return i.getElementsByTagName("category")[0].firstChild.nodeValue;

 86:                     },

 87:                     function() { return "" }

 88:                 ),

 89:                 "title" : i.getElementsByTagName("title")[0].firstChild.nodeValue,

 90:                 "link"    : i.getElementsByTagName("link")[0].firstChild.nodeValue,

 91:                 "description" : Try.these (

 92:                     function() {

 93:                         return i.getElementsByTagName("description")[0].firstChild.nodeValue;

 94:                     },

 95:                     function() { return "" }

 96:                 ),

 97:                 "pubDate" : Try.these (

 98:                     function() {

 99:                         return i.getElementsByTagName("pubDate")[0].firstChild.nodeValue;

100:                     },

101:                     function() {

102:                         return i.getElementsByTagName("dc:date")[0].firstChild.nodeValue;

103:                     },

104:                     function() { return "" }

105:                 )

106:             })

107:         })

108:         this.item = items;

109:     }

110: });

111:

112: var rssParser1 = Class.create();

113: Object.extend(rssParser1.prototype, {

114:     initialize    : function(xml) {

115:         var channel = xml.getElementsByTagName("channel")[0];

116:         var images  = xml.getElementsByTagName("image")[0];

117:         this.title  = channel.getElementsByTagName("title")[0].firstChild.nodeValue;

118:         this.link   = channel.getElementsByTagName("link")[0].firstChild.nodeValue;

119:         this.image  = {

120:             "url"   : images.getElementsByTagName("url")[0].firstChild.nodeValue,

121:             "title" : images.getElementsByTagName("title")[0].firstChild.nodeValue,

122:             "link"  : images.getElementsByTagName("link")[0].firstChild.nodeValue

123:         };

124:         this.description = channel.getElementsByTagName("description")[0].firstChild.nodeValue;

125:         this.language    = channel.getElementsByTagName("dc:language")[0].firstChild.nodeValue;

126:         this.pubDate     = channel.getElementsByTagName("dc:date")[0].firstChild.nodeValue;

127:         var items        = xml.getElementsByTagName("item");

128:         var itemValue    = new Array();

129:         for(var i = 0; i < items.length; i++) {

130:             itemValue.push({

131:                 "category"   : items[i].getElementsByTagName("category")[0].firstChild.nodeValue,

132:                 "title"      : items[i].getElementsByTagName("title")[0].firstChild.nodeValue,

133:                 "link"       : items[i].getElementsByTagName("link")[0].firstChild.nodeValue,

134:                 "description":

135:                               items[i].getElementsByTagName("description")[0].firstChild.nodeValue,

136:                 "pubDate"    : items[i].getElementsByTagName("dc:date")[0].firstChild.nodeValue

137:             });

138:         };

139:         this.item = itemValue;

140:     }

141: });

142:

143: var rssParserAtom = Class.create();

144: Object.extend(rssParserAtom.prototype, {

145:     initialize    : function(xml) {

146:         this.title   = xml.getElementsByTagName("title")[0].firstChild.nodeValue;

147:         this.link    = xml.getElementsByTagName("link")[0].getAttribute("href");

148:         this.image   = {

149:             "url"    : "",

150:             "title"  : "",

151:             "link"   : ""

152:         };

153:         this.description = xml.getElementsByTagName("info")[0].firstChild.nodeValue;

154:         this.language    = "";

155:         this.pubDate     = xml.getElementsByTagName("modified")[0].firstChild.nodeValue;

156:         var items        = xml.getElementsByTagName("entry");

157:         var itemValue    = new Array();

158:         for(var i = 0; i < items.length; i++) {

159:             itemValue.push({

160:                 "category"   : "",

161:                 "title"      : items[i].getElementsByTagName("title")[0].firstChild.nodeValue,

162:                 "link"       : items[i].getElementsByTagName("link")[0].getAttribute("href"),

163:                 "description":items[i].getElementsByTagName("summary")[0].firstChild.nodeValue,

164:                 "pubDate"    : items[i].getElementsByTagName("created")[0].firstChild.nodeValue

165:             });

166:         };

167:         this.item = itemValue;

168:     }

169: });

 

function rssParser(xml) {...}을 보시면 Rss의 버전을 구해서 알맞은 Class를 호출 하는 부분 입니다.
Try.these (...)
부분이 보이실 것입니다. API 보기
아래에 나열된 function()을 수행하여 먼저 성공한 하나만을 호출 합니다. 정말 멋진 생각입니다. ^^;

var rssParser2 = Class.create();
Object.extend(rssParser2.prototype, { ... }
새로운 Class를 생성하여 prototype을 확장 하였습니다.
Class.create()
로 확장을 하면 항상 initialize 를 수행 합니다.
new rssParser2(xml);
하게 되면 자동으로 initialize 부분이 수행이 된다는 의미 입니다.

var channel = xml.getElementsByTagName("channel")[0];
태그명 channel의 첫번째 element를 가져 옵니다. channel이 한개뿐인데 배열로 가져 왔습니다.
태그명으로 가져 올 때는 getElementsByTagName 여기서도 보실수 있듯이 복수형입니다.
그래서 항상 배열형태로 리턴이 됩니다. [0] 이분이 빠지면 에러가 납니다. ^^;

이제 channel의 자식 노드들을 뽑아올 차례 입니다.

this.title  = channel.getElementsByTagName("title")[0].firstChild.nodeValue;
<channel>
아래에 있는 태그명 <title>의 첫번째 노드의 value를 가져 옵니다.
DOM
에 대한 자세한 내용은
URL : http://www.ibm.com/developerworks/kr/library/wa-ajaxintro5/
을 참고 하시기 바랍니다.

이렇게 title, lilnk, descriiption을 가져옵니다.
pubDate
의 경우는 먼저 pubDate 태그를 찾은 후 없으면 lastBuildDate를 그것도 없으면 공백문자를 리턴해 줍니다.
뭐 어려운거 없습니다. 그냥 DOM으로 노드 뽑아 오듯이 하나씩 뽑아서 대입해 주면 됩니다.

<item>태그에 들어있는 각각의 post를 가져올 차례입니다.
$A(channel.getElementsByTagName("item")).each(function(i){ ... })
$A
Array의 확장판으로 Ruby틱한 배열형태를 사용할 수 있도록 해 줍니다. API 보기
<item>
을 돌면서 <item>에 포함한 <title>, <link>, <description>, <pubDate>를 가져와서 JSON형태로 파싱하고
파싱된 것들을 item이란 변수에 배열로 담아 놓습니다.
이렇게 해 놓으면 후에 item[i].title, item[i].link, item[i].description 으로 가져올 수 있습니다.


어떻게 글로 설명을 할려니 더 어려워 진거 같네요.
구현된 모습을 한번 보겠습니다.

 
 
 
얼추 비슷합니다. ^^V
이것으로 이번 post는 마치겠습니다.
 
내용도 없고 재미도 없는 글 읽어 주셔서 감사합니다.
 
 

시간이 되면 rssParser.js를 이용한 구글 개인화 페이지를 구현해 보도록 하겠습니다.
물론 prototype.js를 이용할 것이며 Scriptaculous 의 이펙트도 이용할 것입니다.
화면상으로 잠시 맛배기를 ^^;
 
 



2007/07/20 09:59 2007/07/20 09:59
이 글에는 트랙백을 보낼 수 없습니다

2. Rss XML로 가져오기

 

이제 가져올 element는 정해 졌습니다.
하지만 한가지 문제가 있습니다. 이놈을 어떻게 가져 올 것인가???

 

Ajax라고 많이 들어 보셨을 것입니다. 일명 HTML, Javascript DHTML, DOM으로 구성된 비동기 통신을 가리키는 말입니다.
Ajax
의 핵심에는 XMLHttpRequest 가 있습니다. 대충 보니 HTTP프로토콜을 이용하여 XML로 어떤 값을 받는거 같습니다
.
이놈이 사용자가 눈치채지 못하게 어떤 값을 가져오고 그것을 DOM형태로 소리 소문 없이 문서 사이에 끼워 넣으면서 여러가지 동적인 화면을 구성할 수 있습니다.


Ajax
에 대해서 더 자세히 아시고 싶으신 분은
URL :
http://www.ibm.com/developerworks/kr/library/wa-ajaxintro1.html
를 참고해 주세요.
Post의 주제가 초간단 Rss Reader를 만드는 것이기 때문에 주제에 충실 하도록 하겠습니다.

 

 

이제 보실 부분은 rssReader.htmljavascript 부분입니다.
3개의 function으로 구성되어 있습니다.

 

uriChk()
입력한 URL값이 유효한 값인지 검사 합니다. 최소한 http://XXX.XXX https://XXX.XXX 형태가 되는지 검사합니다.
여기를 통과하면 일단 유효한 URL이라고 판단하여 getRss(URL)을 호출 합니다.


getRss(URL)
이부분이 핵심입니다. 해당 URL Ajax비동기 통신을 하여 xml을 가져 옵니다.
그리고 var xml = rssParser(req.responseXML); xml JSON으로 파싱을 시킵니다
.
rssParser
rssParser.js include javascript에 들어 있습니다
.
만약 알맞게 JSON으로 파싱이 되었다면 printHTML(JSON) 을 호출 합니다.


printHTML(JSON)
이 부분은 그냥 이뿌게 꾸며서 <div id="title"></div> 여기에 제목 부분을 넣고
<div id="contents"></div>
여기에 item 리스트를 넣는 것입니다.

 

 

전체 소스를 보겠습니다.

 

 0: <?xml version="1.0" encoding="EUC-KR" ?>
 1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2: <html xmlns="http://www.w3.org/1999/xhtml">
 3: <head>
 4: <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR" />
 5: <style type="text/css">
 6:     *         {font-size:12px;}
 7:     body      {margin:0px; padding:0px;color:#666666;}
 8:     a:active  {color:#236fa5; text-decoration:none;}
 9:     a:visited {color:#236fa5; text-decoration:none;}
10:     a:link    {color:#236fa5; text-decoration:none;}
11:     a:hover   {color:#518fbb; text-decoration:underline;}
12: </style>
13: <script type="text/javascript" language="javascript" src="./prototype.js"></script>
14: <script type="text/javascript" language="javascript" src="./rssParser.js"></script>
15: <script type="text/javascript" language="javascript">
16: <!--
17:     function uriChk() {
18:         var uri = $("uri");
19:         if(uri.value == "") {
20:             alert("가져올 피드의 주소를 입력 하세요.");
21:             uri.focus();
22:             return;
23:         }
24:         if(!(/(http|https):\/\/\w+([-.]\w+).+$/).test(uri.value)) {
25:             alert("피드 주소가 유효하지 않습니다.");
26:             uri.focus();
27:             return;
28:         }
29:         getRss(uri.value);
30:     }
31: 
32:     function getRss(uri) {
33:         Element.hide("title");
34:         $("contents").innerHTML = $("loading").innerHTML;
35:         new Ajax.Request(
36:             uri,
37:             {
38:                 method : "get",
39:                 onSuccess : function(req) {
40:                     var xml = rssParser(req.responseXML);
41:                     if(!xml) { 
                            alert("Rss
포멧이 아닙니다.");
                            $("contents").innerHTML = "";
                            return;
                        }
42:                     printHtml(xml);
43:                 },
44:                 onFailure : function() {
45:                     alert("데이타를 가져오는데 실패 했습니다.");
46:                     $("contents").innerHTML = "";
47:                 }
48:             }
49:         );
50:     }
51: 
52:     function printHtml(xml) {
53:         var d = new Date(xml.pubDate);
54:         var title = '';
55:         var cont = '';
56:         title += '<a href="' + xml.link + '" target="_blank"><strong style="font-size:16px;">' 
                  + xml.title + '</strong></a>';
57:         if(d != 'NaN') {
58:             title += '<span style="padding-left:20px;">(' + d.toLocaleString() + ')</span>';
59:         }
60:         title += '<br/><br/>' + xml.description;
61:         for(var i = 0; i < xml.item.length; i++) {
62:             cont += '<a href="' + xml.item[i].link +'" target="_blank">'
                     + '<strong style="font-size:16px;">' + xml.item[i].title + '</strong></a>';
63:             cont += '<hr style="height:1px;color:518fbb"/>';
64:             if(xml.item[i].pubDate != "") {
65:                 var pd = new Date(xml.item[i].pubDate);
66:                 cont += '<span style="color:#aaaaaa;">' + pd.toLocaleString() 
                         + '</span><br/><br/>';
67:             }
68:             cont += xml.item[i].description.replace("\n", "<br/>") + '<br/><br/><br/>';
69:         };
70:         $("title").innerHTML = title;
71:         $("contents").innerHTML = cont;
72:         Element.show("title");
73:     }
74: //-->
75: </script>
76: <title>Rss Reader V 0.1 by Vricks - www.vricks.com</title>
77: </head>
78: <body>
79: <div style="width:800px;text-align:left;margin:auto;">
80:     <div style="background-color:#ff000a;padding:16px 0 50px 16px;margin:20px 0;">
81:         <strong style="font-size:14px;color:#ffffff;">
82:             Rss Reader V0.1
83:         </strong>
84:     </div>
85:     <div style="text-align:center;margin:20px;">
86:         <input type="text" id="uri" value="http://blog.rss.naver.com/jeffyang.xml" 
             style="width:360px;" />
87:         <input type="button" value="RSS 가져오기" onclick="uriChk();" />
88:     </div>
89:     <div id="title" 
             style="padding:20px;background-color:#f7f7f4;border:1px solid #aaaaaa;display:none;"
>
        </
div>
90:     <div id="contents" style="padding:20px;"></div>
91:     <div style="display:none;" id="loading"><div style="text-align:center;">
             <
img src="./loading.gif" alt=""
/>
        </
div></div>
92: </div>
93: </body>
94: </html>

$("uri") -> API 보기
document.getElementById("uri")
와 같은 말입니다. 물론 $()함수는 더 많은 기능을 제공해 줍니다.

Element.hide("title"); -> API 보기
document.getElementById("title").style.display = "";
과 같은 말입니다. 물론 Element.hide("title") 함수가 더 많은 기능을 제공합니다.

 

var myAjax = new Ajax.Request(
    url,
    {
        method: 'get',
        parameters: params,
        onSuccess: function(req) {
            something...
        },
        onFailure: function() {
            something....
        }
    }  
);

XMLHttpRequest
를 쉽게 구현해 주는 부분입니다. -> API 보기
url :
접속할 URL입니다. 뒤에 오는 method get일 경우 get방식의 prameters를 붙여서 사용할 수 있습니다.
) var url = "http://test.com/test.xml?id=1234&date=2007-03-01";
method : get/post
가 올 수 있습니다
.
parameters : get
방식의 경우 null 이 옵니다. post의 경우는 ?를 제외한 prameter를 붙여서 사용 합니다
.
) var params = "id=1234&date=2007-03-01";
onSuccess :
성공 했을때의 코드를 기술 합니다
.
onFailure :
실패 했을때의 코드를 기술 합니다.


printHTML()
JSON으로 파싱된 값들을 받아서 뿌려 주는 일을 합니다.
var xml = rssParser(req.responseXML);
로 받아서 printHTML(xml)로 보냈습니다
.
channel
title을 가져 올 때는 단순히
xml.title
item[0]
title을 가져 올 때는 xml.item[0].title 이렇게 가져 오면 됩니다
.
rssParser.js
는 다음 post에서 만나 보실 수 있습니다.

 

2007/07/20 09:58 2007/07/20 09:58
이 글에는 트랙백을 보낼 수 없습니다

언제부터인지 우리 주위에 Rss라는 단어가 많이 익숙해져 있습니다.
블로그, 뉴스, 도서 사이트 심지어 UCC까지 WEB2.0시대인 요즘은 원하는 정보를 Rss 서비스로 받아 보실 수 있습니다
.
이러한 Rss 피드를 모아서 편하게 구독하게 해 주는 것이 일명 Rss Reader입니다
.
Rss Reader
에는 웹 기반의 Rss Reader, 설치형 Rss Reader, 동기화 가능 설치형 Rss Reader이 있습니다
.
이러한 것들을 구현하기 위해서는 수집, 정렬, 검색, 동기화 등등 많은 기술들이 들어 갑니다
.
하지만 버뜨~ post의 제목이 초간단 Rss Reader입니다
.
제목에 충실히 인터넷 연결이 되어 있고 IE가 깔려 있으면(흠 이부분은 나중에 이야기 드릴께요 ㅠㅠ) 단순히 해당 피드 주소만 넣으면 읽기만 가능한 그러한 Reader를 만들어 보겠습니다
.
누군가 그랬습니다
. Simple is The Best....
사실 실력이 요까지라서 더 복잡한건 ..... OTL

 

초간단 Rss Reader를 구현하기 위한 기본적인 지식으로 Javascript, html, xml의 기본적인 지식이 필요합니다.
Javascript
의 경우 prototype.js를 기본으로 하여 이미 구현한 라이브러리를 최대한 이용하도록 하겠습니다
.
prototype.js
URL : http://www.prototypejs.org 에서 다운 받으실 수 있으며

http://blog.naver.com/jeffyang/150015619963
에 가시면(post 자랑 ^^;) 한글로 된 메뉴얼의 링크를 얻으실 수 있습니다.
Post 작성하는 순간에 버전이 1.51 RC1으로 올랐습니다
.
이번 버전에는 XML노드를 처리하기 위한 라이브러리가 보이는 군요
.
애써 외면하며 여기서는 prototype 1.5를 기준으로 하겠습니다.

 

간략한 post의 순서입니다.

 

1. Rss를 분석하여 어떤 elements를 가지고 표현할 것인지 결정 합니다.
2. Rss
XML로 받아 옵니다. 이 부분은 Javascript XMLHttpRequest객체를 가지고 구현을 합니다
.
3.
받아온 XML을 파싱해야 합니다. 이 부분 역시 Javascript JSON 포멧으로 파싱 합니다
.
4. JSON
을 받아서 화면이 html코드로 이쁘게 뿌려 주면 간단 RssReader의 완성 입니다.


Javascript, html, xml
로만 하겠다고 해 놓고 JSON이란 놈이 보이는 군요.
JSON
JavaScript Object Notation 라 불리우는 경량의 DATA 교환 형식입니다
.
Javascript
맞죠!!! 네 분명 Javascript Object라고 했습니다. JSON에 대해서 더 자세히 알고 싶으신 분은

URL : http://json.org/json-ko.html
을 참고 하세요. 한글로 잘 설명되어 있습니다.
평소 영어를 외계어로 여기는 까닭에 해석하시는 수고를 덜어 드리고자 이렇게 친절하게 한글주소로 안내를 해 드립니다.


이쯤에서 첫번째 주제 Rss를 분석해 보도록 하겠습니다.


현재 국내에서 가장 많이 사용되고 있는 것이 Rss 2.0 포멧입니다. 그리고 Rss 1.0, ATOM 포멧도 많이 보입니다.
솔직히 웬만한 사이트들은 모두 Rss 2.0을 지원해 줍니다. 하지만 국내 사이트들 중에는 무늬만 Rss 2.0이고 표준을 지키지 않은 곳들이 많이 있습니다
.
표준을 준수하지 않은 경우는 해당 Rss서비스에 맞도록 하나씩 처리를 해 줘야 합니다
.
필수 Elements가 빠진곳이나 엉뚱한 Elements가 나타나는 곳들도 종종 있었습니다
.
한국에서 구글이 힘을 못쓰는 이유는 포탈 및 대형 사이트 들의 비표준이 지대한 공헌을 했다고 하더군요. (공감 100% 입니다
.)
어찌 되었건 한국어로 된 Rss 서비스를 받고자 한다면 이 정도 수고는 해야 하는군요. ㅠㅠ

이러한 비표준을 처리하기 위해서 prototype.js Try.these 구문을 이용 했습니다.

 

1. RSS 2.0 분석

 

Rss란 무엇인가?
한국정보통신기술위원회의 문서를 빌리자면

 

RSS 는 웹 컨텐츠 신디케이션 형식으로 Really Simple Syndication 의 약자이다.
컨텐츠 신디케이션(Content Syndication)이란 사이트 컨텐츠의 일부 또는 전체를 다른 서비스에서 이용할 수 있도록 해주는 것을 말한다
.
신디케이션된 컨텐츠(또는 feed)는 컨텐츠 자체와 메타데이타 로 구성된다. 이러한 feed 에는 헤드라인 내용만 있을 수도 있고, 스토리에 대한 링크만 있을 수도 있으며, 사이트의 전체 컨텐츠가 포함될 수도 있다
.
근래에 들어 특히 1 인 미디어인 블로그(blog)를 중심으로 컨텐츠 신디케이션 표준인 RSS 의 가능성을 확인되면서, 다양한 응용들이 등장하고 있고, 차세대 웹 콘텐츠 유통 기술의 핵심 표준으로 주목받고 있다
.


뭔 소린지 잘 모르겠습니다. 그냥 구조를 한번 살펴 보죠.
대략 다음과 같은 구조로 되어 있습니다. 이중 굵게 처리된 것들은 필수 요소 입니다
.
item
태그의 title, link, description은 모두 필수는 아니고 세개 중 한개는 반드시 있어야 합니다.

 

 0: <?xml version="1.0" encoding="euc-kr" ?>
 1: <rss version="2.0">
 2:     <channel>
 3:         <title>채널의 제목</title>
 4:         <link>채널의 링크</link>
 5:         <description>채널에 대한 간략한 설명</description>
 6:         <language>채널이 작성되는 언어</language>
 7:         <copyright>채널의 저작권 통지</copyright>
 8:         <managingEditor>채널 편집 담당자의 전자우편</managingEditor>
 9:         <webMaster>채널 웹마스타의 전자우편</webMaster>
10:         <pubDate>채널 컨텐츠의 발행 시간</pubDate>
11:         <lastBuildDate>채널이 마지막으로 변경된 시간</lastBuildDate>
12:         <category>채널이 속해있는 가테고리</category>
13:         <generator>채널을 생성하는데 사용된 프로그램</generator>
14:         <images>
15:             <url>이미지의 URL</url>
16:             <title>이미지의 제목</title>
17:             <link>해당 사이트의 링크</link>
18:             <width>이미지 넓이</width>
19:             <height>이미지 높이</height>
20:         </images>
21:         <item>
22:             <title>항목의 제목</title>
23:             <link>항목의 URL</link>
24:             <description>항목의 내용</description>
25:             <author>저작자의 전자우편</author>
26:             <category>항목이 속해있는 가테고리</category>
27:             <comments>항목과 관련된 설명 페이지의 URL</comments>
28:             <guid>고유하게 항목을 식별할 수 있는 문자열</guid>
29:             <pubDate>항목 발행 시간</pubDate>
30:         </item>
31:     </channel>
32: </rss>

 

어렵습니다. ㅠㅠ.
이것만 봐서는 눈에 잘 안 들어 옵니다. 그래서 IE 7.0의 피드랑 비교해서 각 항목을 알아 보겠습니다.

 

 

<IE 7.0 피드 그림>

 

 

대충 감이 옵니다.
화면을 보아 하니 <channel>에서 시작해서 </channel>로 닫히는군요
.
<channel>
아래에 있는 <link><title><pubDate>도 대충 어떤 놈인지 알겠습니다
.
중간에 <image>도 보이구 <category>도 보입니다
.
<item>
에서 시작해서 </item>까지가 한개의 post 로 구분되어 있습니다
.
<item>
아래에는 <link><title><pubDate><description>이 보이네요..

이제 감 잡았습니다.
가져올 elements를 구분하겠습니다
.
피드의 titlelink
, pubDate(없으면 lastBuildDate)를 가져와서 있는놈들을 큰 제목에 뿌려 주고

item
을 배열로 돌리면서 item의 자식들 link, title, pubDate, desciption 중 있는놈들을 모두 뿌려 주겠습니다.


2007/07/20 09:57 2007/07/20 09:57
이 글에는 트랙백을 보낼 수 없습니다
웅쓰:웅자의 상상플러스
웅자의 상상플러스
전체 (379)
게임 (5)
영화 (2)
기타 (23)
맛집 (5)
영어 (2)
대수학 (3)
형태소 (5)
Hacking (9)
Linux (112)
HTML (48)
Application_developing (48)
Web_developing (102)
Window (11)
«   2024/11   »
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
  1. 2016/01 (1)
  2. 2015/12 (3)
  3. 2015/10 (3)
  4. 2015/03 (2)
  5. 2015/01 (4)