prototype.js를 아주 잘 쓰진 않지만 그래도 어느정도 사용해왔었는데 오늘 처음보는 현상을 겪었습니다. prototype.js에는 dom:loaded라는 이벤트가 있습니다. 이 이벤트는 DOM이 모두 로드되면 발생하는 이벤트이고 보통 많이 쓰는 window.onload와는 약간 다릅니다. window.onload보다는 좀 더 빠른 타이밍이라고 할 수 있습니다. DOM만 다 로드되면 호출되니까요. 저는 prototype.js라서 dom:loaded이지만 통칭 domready Event라고 할 수 있고 대개의 자바스크립트 프레임워크에는 이런 이벤트 리스너가 제공되고 있습니다.
이 이벤트에 대해서 단 한번도 문제를 겪은 적이 없었는데 오늘은 황당한 문제를 겪었습니다. dom:loaded 이벤트가 호출되었는데 엘리먼트를 호출하면 null이 나오면서 못찾는 것이었습니다. 약간의 텀을 주고 나면 당연히 다시 다 로드가 되고요. 즉 dom이 실제 다 로드되지 않았는데 dom:loaded가 호출되었다는 것이지요. 소스가 상당히 꼬여있는 상태였고 그걸 정리할 수도 없는 상황이라 고생을 좀 했는데 어쨌든 이유는 찾았습니다.
<html>
<head>
<title>Untitled Document</title>
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js"></script>
<script type="text/javascript">
//<![CDATA[
document.observe('dom:loaded', function() {
alert($("testElement2"));
});
//]]>
</script>
</head>
<body>
<div id="testElement1"></div>
<script type="text/javascript">
//<![CDATA[
$("testElement1").innerHTML = "It's New Text.";
//]]>
</script>
<div id="testElement2"></div>
</body>
</html>
간단히 보자면 위의 상황이라고 할 수 있습니다.
상황을 설명하자면 DOM중간에 스크립트를 넣어서 DOM이 다 로드되지 않은 상태에서 DOM을 동적으로 핸들링시도한 것입니다. 어찌된 일인지 이렇게 할 경우 실제 DOM이 로드되는 타이밍이 아니라 DOM을 동적으로 핸들링하는 시점에서 dom:loaded가 발생해 버립니다. 물론 IE에서만 그러고(역쉬!!) 파이어폭스, 사파리, 오페라, 크롬 등에서는 위와같은 현상이 발생하지 않습니다.
물론 위와같은 코드는 일단 사용법자체가 잘못되어 있습니다. DOM은 기본적으로 완전히 로드된 후에 다루는 것이 기본이지요. DOM이 load되지 않았는데 다룰려고 잘못하면 실패할 가능성이 높기 때문입니다. 위 코드는 그런 기본적인 룰(강제는 아니죠)을 어긴 것이긴 하지만 실제 사이트 소스들을 보면 동적으로 핸들링은 하지는 않더라도 body곳곳에 script태그를 삽입하는 경우를 많이 보곤 합니다. 현업에서 Obtrusive Javascript를 지향하는 것은 거의 보기가 힘들죠 ㅠ..ㅠ
위 예제는 간단히 볼 수 있게 만든 테스트입니다. dom:loaded가 호출되었지만 반이상정도는 null로 찍히는 것을 볼 수 있습니다. 물론 IE에서 테스트했을 때만 발생합니다. 너무 간단한 DOM이라서 위에 설명한 현상이 잘 발생하지 않아서 하단에 스크립트를 약간 두어서 dom load를 지연되도록 했습니다.(로컬에선 반정도만 발생하는데 확실히 웹상에 올리면 거의 발생하는군요.)
prototype.js쪽의 문제라고 해야할지 IE의 문제라고 해야할지 고민을 많이 했는데 이것저것 찾아보니 역시 IE의 문제(?)군요. IE and “Operation Aborted” 이 포스팅을 보면 해당 문제에 대해서 정리를 상당히 할 수 있습니다. 이 포스팅에 나온대로 MS에서도 비슷한 문제에 대해서 정리된 내용이 있지만(위의 포스팅 내용중에 나와 있습니다.) 위 포스팅내용대로 정확히 맞아떨어지지는 않습니다.
정확한 현상은 DOM이 다 로드되지 않았는데 innerHTML이나 appendChild등을 이용해서 동적으로 핸들링을 시도했을 경우 문제가 발생하며 IE는 이를 알려주지도 않습니다. 정확히 어떻게 해결하는지는 모르겠지만 다른 브라우저에서는 원하는대로 잘 작동합니다. IE 6, 7, 8이 다 그런걸 보면 의도적인 걸까요 ㅠ..ㅠ
document.body.appendChild(document.createElement('div'));
오직 dom로드 종료전에 위 코드처럼 dom핸들링이 연속적으로 사용되었을 때만 error 표시를 해줍니다.
IE 8에서는 아주 친절히 알려줍니다. (그냥 단순 innerHTML만 했을때도 알려주면 좀 좋아.. ㅡ..ㅡ) 제가 다 짠 소스는 아니었지만 역시 일반적으로 권장하는 룰은 따라주는게 좋다는 교훈을 다시한번 되새기게 되는군요. 사실 그냥 window.onload를 쓰면 문제 없습니다. 복잡한 소스에 조금이라도 고급(?)기법을 써보려고 하다보니 꼬인 것이긴 하죠. 덕분에 새로운걸 하나 알았지만요.
script에다가 defer="defer" 로 처리하시면 될거 같네요
그렇군요. defer로 처리하는 방법도 있었군요.
defer가 IE에서 약간의 동작 오류가 있다고 본적이 있는것 같기는 한데요.
문제의 해결보다는 저런 문제가 있다는 걸 알게 되서 포스팅한거구요. 혼자의 개발이 아니라 나머지 스크립트들을 원하는 방식으로 수정할 수 없다는게 가장 큰 문제라고 할 수 있겠네요. ㅡ..ㅡ