Monthly Archives: April 2014

버블링을 활용한Event 부여

동적으로 생성되는 DOM 요소에 이벤트를 부여해야 하는 경우가 있습니다.  먼저 Add Button 이라는 이름의 버튼이 하나 있고, 그 버튼을 누를때 마다 More 라는 버튼이 생성된다고 해봅니다. 그리고 More 버튼을 누를때 마다 console에 More! 라는 메세지를 띄웁니다. 먼저 HTML 문서는 다음과 같이 구성합니다.

<body>
  <button id="add-button">Add Button</button>
  <div id="button-area">
  </div>
</body>

그리고 Javascript(jQuery를 활용합니다)는 다음과 같이 작성합니다.

$('#add-button').on('click', function($event) {
  $event.preventDefault();
  var code = '&lt;button class="more"&gt;More&lt;/button&gt;';

  $('div#button-area').append( code );
  $('.more').on('click', function( $event ) { 
    $event.preventDefault();
    console.log( 'More!' ); 
  });
});

일단 겉보기에는 별다른 문제가 없이 작동합니다. 하지만 콘솔창을 띄워보면 문제가 발생한 것을 알 수 있습니다. More 버튼을 3개 생성한 후 More 버튼을 눌러보면 첫번째 버튼을 클릭했을 때 콘솔창에는 More! 메세지가 3번씩, 두 번째 버튼을 2번씩 생성됩니다.

버튼이 생성될 때마다 ‘more’ 라는 클래스를 가진 DOM 요소에 이벤트를 부여했기 때문에 먼저 생성된 more 버튼에는 버튼을 생성한 횟수만큼 이벤트가 쌓입니다. 이를 다음과 같이 수정해 보겠습니다.

$('#add-button').on('click', function($event) {
  $event.preventDefault();
  var code = '<button class="more">More</button>';

  $('div#button-area').append( code );
  $('.more').off('click').on('click', function( $event ) {  
    //.off('click') 이 추가되었습니다.
    $event.preventDefault();
    console.log( 'More!' ); 
  });
});

이제 정상적으로 작동합니다. 버튼이 생성될 때 일단 more 클래스를 가진 모든 DOM 요소의 click 이벤트를 제거한 다음 다시 click 이벤트를 부여하기 때문이죠. 하지만 뭔가 꺼림직합니다. 버튼을 누를 때마다 Javascript는 모든 more 버튼에 이벤트를 제거하고 생성하는 작업을 반복합니다. 불필요한 자원 낭비죠. 처음 한번만 이벤트를 생성할 수는 없을까요? 가능합니다.

$('div#button-area').on('click', '.more', function( $event ) {
  $event.preventDefault();
  console.log( 'More!' ); 
});

$('#add-button').on('click', function($event) {
  $event.preventDefault();
  var code = '<button class="more">More</button>';

  $('div#button-area').append( code );
});

Add button은 More 버튼을 생성하는 기능만을 수행하고 이벤트를 부여하는 코드를 밖으로 뺐습니다. 근데 이벤트가 More 버튼이 아니라 그 상위 요소인 div#button-area에 지정되어 있군요! 그리고 on() 함수의 두번째 매개 변수가 callback 함수가 아닌 ‘.more’로 변경되어 있습니다. 일단 이 코드는 정확하게 우리가 원하는데로 작동합니다.

이와 같은 현상(?)은 이벤트 버블링이라는 자바스크립트 기능때문에 발생합니다. div#button-area를 포함한 이 DOM 요소의 자식요소에서 클릭 이벤트가 발생하면 누가 이 이벤트를 발생시켰는지 확인합니다. 그리고 on() 함수의 두번째 매개변수로 지정한 ‘.more’ 가 클릭이벤트를 발생시킨 범인(?)으로 확인되면 callback 함수가 비로소 실행됩니다. 이렇게 되면 div#button-area가 존재하는 한 몇개의 More 버튼이 생성되더라도 이벤트는 항상 발생하고, 중복으로 이벤트가 발생하는 일도 없어집니다.

[vs 놀이] 무엇이 더 빠를까?

jQuery는 Javascript 를 사용하는데 있어서 없어서는 안 될 라이브러리가 되었습니다.  하지만 편리함에 비례해 대단히 무거운 녀석입니다.  간단한 사이트나 어플리케이션에서는 눈에 띄는 문제를 일으키지 않지만 프로젝트의 규모가 커지거나 기준이 되는 디바이스의 성능이 낮은 경우 높은 확률로 퍼포먼스 이슈가 발생합니다.

특히 퍼포먼스 문제가 항상 따라다니는 하이브리드 웹 앱의 경우는 특히 신경쓸 부분이 많습니다.  어떻게 코딩하는 것이 좀더 빠른 성능을 낼 수 있을까요. 그래서 남녀노소 누구나 좋아하는 vs 놀이를 해보기로 했습니다.  실험 출처는 http://jsperf.com/ 입니다. 심심할때 한번 들러서 둘러보면 재미있습니다(엉터리 실험도 많으니 소스를 주의 깊게 살피셔야 합니다)

 

Case 1. getElementById() vs querySelector() vs jquery() : ID Selector

var case1 = document.getElementById('target');
var case2 = document.querySelector('#target');
var case3 = $('#target');

가장 느린 결과를 보인 것은 jquery()입니다.
그 다음은 querySelector()로 jquery에 비해 무려 10배 가까운 성능차이를 보였습니다.
가장 빠른 결과는 getElementById()로 querySelector()보다 2배 가까이 빨랐습니다.

 

Case 2. getElementsByClassName() vs querySelectorAll() vs jquery() : Class Selector

var case1 = document.getElementsByClassName('target');
var case2 = document.querySelectorAll('target');
var case3 = $('.target');

가장 빠른 것은 예상데로 네이티브 코드인 getElementsByClassName() 이었습니다.
다소 의외였던 것은 querySelectorAll()과 jquery()의 성능차이가 거의 나지 않았다는 것입니다(참고로 getElementByClassName()은 2배 정도 빨랐습니다).  querySelecorAll이 전반적으로 약간 빠르긴 했습니다만 좋은 환경일 수록 성능은 비슷했고 일부 환경에서는 오히려 jquery가 더 빠른 케이스도 있었습니다.  다만, 구형 안드로이드 기기나 저사양 환경에서는 querySelecor가 우위에 있었습니다.

 

Case 3. className vs setAttribute vs prop() vs attr() vs addClass()

이 실험은 다소 설명이 필요합니다.  각 요소에 class가 하나씩 붙어있고 이 클래스를 지우고 새로운 클래스를 부여하는 작업입니다.  따라서 addClass()의 경우에는 기존 클래스를 삭제하는 removeClass() 라는 단계가 하나 더 존재하게 됩니다.

var elements = $('.class'), i;
var eLength = elements.length;
for( i = 0; i < eLength ; i++ ) {
  elements[i].className = 'change'; // case. 1
  elements[i].setAttribute( 'class', 'change' ); // case. 2
  elements[i].prop( 'class', 'change' ); // case. 3
  elements[i].attr( 'class', 'change' ); // case. 4
  elements[i].removeClass('class').addClass('change'); //case 5
}

반복문 안의 소스들은 케이스 별로 별도로 시행되었다고 가정하겠습니다.
우선 className과 setAttribute가 가장 빨랐으며 이 두 케이스의 성능 차이는 거의 나지 않았습니다.
그 다음은 prop()와 attr()로 위의 경우보다 1/2 정도의 성능을 냈습니다.
마지막으로 removeClass + addClass 가 가장 느렸으며 위 경우보다 1/2정도의 성능을 냈습니다.

 

결론 : 네이티브 Javascript 쪽이 성능면에서는 2배 이상의 우위를 보였습니다.  하지만 성능을 위해 jQuery를 포기하기에는 넘어야 할 산이 너무나 많습니다. 일단 jQuery가 압도적으로 구문의 길이기 짧습니다. 그리고 세번째 실험의 경우 하나의 요소에서 여러 클래스를 관리하는 경우 네이티브 Javascript만으로 해결하다가는 지옥(…)을 경험하실 수 있습니다.  그리고 성능 이전 문제로 event 처리나 $.ajax()는 jQuery가 압도적으로 편리합니다.

따라서 규칙을 정해서 네이티브 Javascript와 jQuery를 적절하게 섞어서 사용하는 것을 권장합니다.  물론 이 경우 가독성을 해치거나 스파게티 코드가 탄생할 가능성도 배제할 수 없습니다. 역시 생산성과 어플리케이션의 퍼포먼스라는 두 토끼를 모두 잡기란 쉽지 않은 듯 합니다.