본문 바로가기

web/jQuery

script로 HTML DOM을 생성/추가 하면 왜 bind()/on()이 작동하지 않을까?

언제나 그렇듯이 개발하는 도중에 가장 많이 깜박(그러면 안되지만!)하는 부분이 있습니다.

바로 스크립트로 HTML DOM을 추가하고 기존의 이벤트가 잘 돌아가겠지 하는것입니다.



<div id="test">

    <button>ADD</button>

    <ul>

        <li>test0</li>

        <li>test1</li>

        <li>test2</li>

    </ul>

</div>

<script type=" text/javascript ">      

    jQuery(function($){

         $( "#test button ").on( "click" , function() {

            $( "#test ul" ).append( "<li>test" +$('li').length+ "</li>" );

        });          


        $( "#test li" ).on( "click" , function() {     

            alert($(this).text());     

        });           

    });  

</script>



위의 소스는 버튼을 클릭하면 li을 생성하며 li를 클릭시 li의 text를 alert로 띄워주는 소스입니다. 위의 소스의 문제를 눈치 체셨나요? 결과물은 아래와 같습니다.



문제점은 기존에 미리 생성된 DOM에는 이벤트가 적용되지만 새롭게 생성/추가된 DOM에는 이벤트가 적용되지 않습니다.

해당 소스의 문제점은 아래의 소스로 해결할수 있습니다.



<script type=" text/javascript ">      

    jQuery(function($){

        $( "#test" ).on( "click" , "#button", function() {

            $("#test ul").append("<li>test" +$('li').length+ "</li>");

        });          

        $(document ).on( "click" , "li", function() {              

            alert($(this).text());          

        });         

    });  

</script>



문제 해결!!

이라 쓰고 글을 끝낸다면 이글을 쓴 이유가 없겠지요.(위의 정보는 구글에서 치면 바로 나옵니다.)

문제는 왜 이런 현상이 일어나느냐 입니다.

"DOM를 생성해서 그런거 아닌가요?" 네 맞습니다. 더욱 중요한것은 "javascript는 DOM를 잘 생성했지만 왜 이벤트 연결은 못시키지?" 입니다.

1. 사건 해결의 실마리는 document ?

위에서 해결방법으로 셀렉터를 document로 바꿨더니 정상 작동하였습니다. 그렇다면 document는 무엇일까요? (참고로 jquery에서의 document 는 DOM document 를 가르킵니다.)

W3C 에서는 DOM document 를 다음과 같이 정의하고 있습니다.

  • The document itself is a document node
  • All HTML elements are element nodes
  • All HTML attributes are attribute nodes
  • Text inside HTML elements are text nodes
  • Comments are comment nodes


즉 Document 개체는 DOM의 스팩이자 문서 전체를 대표하는 최상위 개체입니다. 브라우저에서 보이는 모든 HTML들을 품고 있는 개체 입니다.

해결 방법의 소스에서 $(document).on()를 써서 해결할수 있었던 이유는 바로 시작점부터 모든 DOM탐색을 거쳐서 하위단까지 클릭된 객체를 탐색하게 됩니다. 이때 셀렉터로 선언된 <li> 와 같다고 판단하여 이벤트가 발생하게 됩니다. 사실 document개체까지 쓸 필요는 없습니다. 상위노드인 <ul>를 셀렉트로 하고 이벤트를 걸어주면 됩니다.(물론 어떤 노드에서 정확히 이벤트가 일어났는지까지 판단해야 하는 로직이 필요합니다.) 즉 새롭게 생성된 DOM의 상위 DOM(새롭게 생성된 DOM이 아닌 지속적으로 있었던 DOM)을 이용하면 이벤트 발생이 가능하다가 됩니다.


2. jquery.on() 때문인가?

on()메소드는 태어난지 얼마 안된 메소드 입니다. 이전엔 live(), bind(), delegate() 등의 메소드를 이용하여 이벤트를 걸어주는 역활(이벤트 맴핑)을 했었습니다. 하지만 on()은 jquery 1.7.x 이후 버전에서 live(), bind(), delegate()를 대체하는 메서드로 이벤트 핸들러 바인딩에 필요한 기존 메서드의 모든 기능을 제공하면서 live()는 jquery 1.7.x 버전 이후부터 폐기되었습니다. 폐기전 함수들은 각기 다른 역활로 이벤트를 맴핑을 했었습니다. bind()는 HTML문서에 로드가 완료된 DOM객체들만 이벤트 맴핑을 live()는 동적/앞으로 생성될 DOM개체 들에게 이벤트 맴핑을 하는 역활 이었습니다. 이렇듯 같은 이벤트 맵핑을 하는 동작을 두가지 함수로 나뉘다 보니 on()으로 통합하게 된것입니다.

즉 "기존에도 새롭게 생성된 DOM개채에 대해서는 jquery 로직을 따로 만들어줘야 했다." 그리고 처음 소스에서의 on()함수가 이벤트 맴핑을 못했다는것은 "on()함수도 해당 DOM개체의 생성에 대한 이벤트 맵핑을 할수가 없다. (document 같이 상위 개체를 거치지 않는이상)" 가 됩니다. 그러면 여기서 새로운 의문점이 나오게 됩니다. "DOM개체의 script 이벤트 맵핑은 언제 해주는걸까?" 입니다.

3. 브라우저를 알아보자.



출처 : http://www.html5rocks.com/en/tutorials/internals/h...

위의 그림이 우리가 사용하고 있는 브라우저 계층을 도식화 한것입니다.

  • 유저 인터페이스- 주소표시줄/다음이전 버튼 등의 우리가 보고 있는 화면출력부분입니다.
  • 브라우저 엔진 - 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.
  • 렌더링 엔진 - 요청한 콘텐츠를 표시. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함.
  • 자바스크립트 해석기 - 자바스크립트 코드를 해석하고 실행.
  • 자료 저장소는 쿠키와 같이 브라우저내에서 저장하는 공간입니다.

우리가 주목해야 할 부분은 랜더링엔진과 자바스크립트 해석기 부분입니다.

랜더링엔진의 경우 여러 엔진이 있지만 가장 많이 사용하고 있는 webkit 엔진의 동작 과정입니다.



출처 : http://www.html5rocks.com/en/tutorials/internals/h...

랜더링 엔진은 html과 css를 DOM개체/Tree로 변환하여 우리가 보고 있는 Layout을 만든후 보여주는 역활을 해주는 부분입니다. (이 엔진의 차이때문에 css에서 브라우저별 핵이 존재하며 선언부 '-webkit-/-moz-/-o-/-ms-' 가 엔진에서 판단하여 해석을 달리해줍니다.) 이 엔진의 마지막 부분인 Display이후인 DOM이 탐색 및 조작될 준비가 되면 그때 자바스크립트 해석기가 스크립트를 작동시킵니다. 이때 스크립트의 함수들에 따라 이벤트가 실행 및 맵핑되게 됩니다! 모든 DOM에 대한 이벤트를 엔진이 총괄하게 되면서 사용자 인터페이스에 대한 반응을 기다리게 됩니다.

즉 우리가 보고 있는 브라우저 화면이 표시되기 직전에 이벤트 맵핑이 이루어집니다. 그리고 해당 이벤트 맵핑은 이미 로드된 DOM개체들에 한해서만 맵핑됩니다. 



결론!!

이벤트 맵핑의 시작점에 의해서 새롭게 DOM이 생성되면 이벤트 맵핑은 걸리지 않습니다. 단 on()함수는 DOM 개체의 이벤트를 항시 체크하기 때문에 브라우저의 Dispaly이후에 생성된 동적 DOM일 지라도 이벤트 맵핑이 가능합니다.


끝!


추가.

"이벤트 맵핑을 위해 $(document).on()대신 $(document).ready(function() {}); 안에 모두 넣으면 되지 않나요?" 하신다면 ready() 함수의 특성상 브라우저 마다 DOMContentLoaded 이벤트 시작점과 이미지의 경우엔 이미지 로드와 ready()의 시작차이 때문에 문제가 발생할수 있습니다. 또한 스크립트의 모듈화 및 캡슐화가 안되므로 재사용 불가능한 소스만 생성할 가능성이 있습니다.



참고사이트.

http://helloworld.naver.com/helloworld/textyle/593...

http://www.html5rocks.com/en/tutorials/internals/h...

'web > jQuery' 카테고리의 다른 글

ECMAScript6  (0) 2016.06.06
jquery 레티나 체크 함수  (0) 2015.09.21
파이어폭스 / IE 에서 scoll 이벤트가 안될때  (0) 2015.09.18
jquery change  (0) 2012.11.21
Useful jQuery Datepicker  (0) 2012.11.20