DOM 2.x 규격 및 최신 IE6,7 에서의 이벤트처리를 이해하기란 쉽지 않다. 따라서 이해하기 편하게 그림을 한장 그려봤다.
아마 이미지를 태그로 표현하자면 아래 정도가 될 듯하다. <body> <div id="d1">
<img id="i1" src="a.gif">
</div>
</body>
자바스크립트는 개별 Element(태그)를 하나의 이벤트 처리단위로 보고 있다. 따라서 body, div, img는 각각 이벤트를 처리할 수 있는 단위가 된다.
1. DOM 2.x 이벤트 모델 특징 DOM2.x모델의 특징은 하나의 이벤트처리유닛이 여러개의 이벤트를 소유할 수 있다는 점이다. 즉 위 그림에 있는 image의 onclick에 함수를 하나만 거는게 아니라 여러개를 걸 수 있다는 뜻이다. 그런 식으로 사고를 확장해보면 같은 onClick 이란 이벤트만 해도 image에 두어개, div에 두어개, body에 두어개 걸 수 있다는 뜻으로 한번 image위를 클릭하면 저 모든 걸어둔 이벤트가 발동하리란걸 예상할 수 있다.
밑에 자주 등장할 용어인 리스너(listener)는 해당 이벤트를 처리할 함수를 뜻한다. 즉 image에 click이벤트를 여러개 걸었다는 것은 click이벤트를 처리할 리스너(함수)를 여러개 등록했다는 뜻이다.
2. 이벤트 전파단계 그러한 이벤트의 전파를 크게 보면 Capture단계와 Bubbling 단계로 나눌 수 있다. Capture단계란 위의 그림처럼 화면의 젤 위에서부터 아래로 이벤트가 전파되는 상황을 말한다. 따라서 이때 이벤트를 처리하려면 해당 이벤트 처리기가 Capture단계에서 작동할 것임을 알려줘야한다. AS3.0, FireFox2.x, Safari3.x, Opera8.x 등 DOM2.x규격을 지키는 프로그램들은 capture단계에서 이벤트가 작동하게 처리할 수 있다. 하지만 IE는 capture단계를 무시한다. 따라서 크로스 플랫폼을 지향한다면 capture단계를 사용해서는 안된다.
bubbling단계에서는 반대로 바닥에 깔린 녀석부터 처리된다. 즉 body에 걸린 이벤트 리스너가 호출되고 div에 걸린게 호출되고 마지막에 image에 걸린 리스너가 호출된다.
그럼 여기서 target단계라는걸 알아보자. target단계라는건 capture단계가 진행중일때 자신에게 이벤트처리할 순번이 돌아온 경우를 말한다. 즉 div입장이라면 capture단계가 진행되다가 div의 이벤트가 호출될 타임이 바로 div입장에서의 target단계라는 것이다. 따라서 당연히 IE엔 capture단계가 없으므로 target단계도 없다.
3. propagation의 개념
propagation이란 이벤트가 전파될 수 있는가를 나타내는 프로퍼티다. 만약 stopPropagation을 호출하면 더이상 이벤트의 전파가 이루어지지 않는다. 즉 위 그림에서 만약 capture단계의 이벤트가 진행중인데 중간의 div가 stopPropagation을 호출하면(리스너에서) body는 이벤트를 전파받을 수 없어 body의 리스너는 호출되지 않는다.
이벤트 자체가 capture단계나 bubbling 단계에 중 한가지모드에서만 작동함으로 stopPropagation의 효과도 마찬가지로 capture단계나 bubbling단계중 한군데만 효과가 있다(즉 누군가 capture단계에서 stopPropagation을 해도 여전히 bubbling단계의 이벤트는 호출될 수 있다) 이러한 propagation의 통제는 DOM2.x 규격에서는 리스너가 인자로 받는 event객체의 메쏘드를 호출하여 처리한다.
function Listener(event){ event.stopPropagation(); }
IE의 경우엔 cancelBubble 속성을 이용하여 처리한다.
function Listener(event){ event.cancelBubble=true; }
IE6.x나 그 이전버전은 리스너의 인자로 event가 전달되지 않는다. 따라서 전역객체 window로 부터 받아올 필요가 있다.
function Listener(event){ if(!event)event=window.event; event.cancelBubble=true; }
여기에 더해 앞의 DOM2.x규격과 호환을 위한 최종적인 propagation 함수는 아래와 같다.
function Listener(event){ if(!event)event=window.event; if(!event.stopPropagation){//IE event.cancelBubble=true; }else{//DOM2.x event.stopPropagation(); } }
속성명을 보면 알 수 있지만 IE입장에서 propagation을 처리하는 속성이 cancelBubble인건 당연하다. 원래 이벤트가 bubbling단계밖에 안일어나기때문에 bubbling을 취소하는 것 자체가 이벤트를 stopPropagation하는 것이기 때문이다. 하지만 DOM 2.x 클라이언트들은 같은 일을 capture단계에서도 할 수 있기 때문에 저런 이름을 가져야한다.
4. default와 preventDefault 이건 해당 event의 실행 성공여부를 임의로 조작하는 기능을 갖는다. 먼저 default에 대한 이해가 필요한데, default 는 사실 함수명이다. 정확히 말하자면 모든 함수는 내부에 default라는 함수를 내장하고 있다고 이해하면 좀 쉽다.
function a(k){ k*=3; return k; }
위의 함수를 보자. 인자를 받아서 3을 곱한후 리턴하고 있다. 이 함수의 리턴값은 당연히 k*3이다. 하지만 마지막 문장인 return k; 가 호출되고 이 함수는 그대로 사라질것 같지만, 실은 그 뒤에 안보이는 일을 한가지 더 한다. 바로 default함수를 호출하는 것이다. 감각적으로 보자면 아래와 같은 느낌이라고나 할까?
function a(k){ k*=3; return k; (default();) }
개발자가 컨트롤하지 않아도 모든 함수는 저런식으로 자동으로 함수가 완전히 끝나는 마지막줄에 default를 호출하도록 되어있다(하지만 이건 어디까지나 DOM 2.x 스펙이고 우리의 IE는 물론 default 호출 따위 없다. 따라서 다르게 구현해야한다) default를 호출하는 이유는 이 함수가 정상적으로 작동하여 작업을 마쳤다는 것을 시스템에게 알리기 위해서다. 시스템은 이벤트가 발생하면 이벤트가 매번 처리되고 리스너가 default를 호출할때마다 해당 리스너에서 이벤트가 정상처리되었으니 그 다음 리스너에게 전달해줘야겠다는 판단을 한다. 만약 이때 default 호출을 막아버리면 어떻게 되는가? 시스템은 뭔가 이벤트 리스너함수가 에러가 났다고 판단하고 더이상 이벤트 전파를 전개하지 않고 정지한다. 요점을 거꾸로 개발자가 통제해 특정 상황에 이용할 수 있게 하는게 바로 preventDefault다. 이 메써드를 호출하면 시스템에게 이벤트가 정상처리되었다는 default호출을 막아버려 이벤트의 전파가 완전히 중지되게 하는 효과가 있다. 개념상으로는 작동하는 방식이 다르지만 결국 stopPropagation과 하는 일은 동일하다. IE는 저런 복잡한 매커니즘을 쓰지 않는다. 리스너가 return false; 통해 false를 반환하면 그 다음번으로 전파된 이벤트는 returnValue를 조사하여 그 값에 따라 어떤 처리를 할지 결정하게 하는 방식을 쓴다. 이건 더 바람직한 면도 있는데 리스너 사이에 어떤 값이나 객체를 넘겨줄 수 있는 표준적인 매커니즘을 제공하기 때문이다. DOM 2.x규격에서는 리스너 사이에 값을 넘길 방법은 Target 자체에 특정 속성을 주고 넘겨받은 리스너는 relatedTarget의 특정 값을 조사하여 얻는 변칙적인 방법밖에 사용할 수 없다(물론 전역변수를 갱신해가면 되긴하지만 그건 열외)
댓글 영역