2009년 1월에, JQuery 창시자 John Resig는 Sizzle이라 불리는 새로운 오픈 소스 javascript 프로젝트를 소개했습니다. 이것은 Standalone CSS Engine이며, JQuery 1.3이레로, 검색 엔진으로 Sizzle을 사용하고 있습니다.
이번 블로그에서는 Sizzle에서 제공하는 Selector(검색)과 Traversing(탐색) 메소드에 대해서 알아보고, 어느 것이 성능이 좋은지? 어떻게 하면 성능을 향상 시킬 수 있는 지를 알아봅니다.
먼저 selecting와 traversing에 대해서 이해하는 것으로 시작하기 위해서, 우선 HTML과 Javascript 예제를 먼저 작성합니다.
<html>
<head>
<title>IT eBooks - Free Download - Big Library</title>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<meta name="description" content="IT eBooks - Free Download eBooks Library">
<meta name="keywords" content="it, book, ebook, free, download, library, lib, books, ebooks, read, online, pdf, direct">
<style>
table thead tr {background-color:blue;}
table tbody tr th {background-color:yellow;}
table tbody tr.alt {background-color:#2EFE2E;}
table tbody tr.alt2 {background-color:#04B486;}
</style>
<script type="text/javascript" src="js/jquery-1.11.1.js"></script>
<script type="text/javascript" src="js/test.js"></script>
</head>
<body>
<div id="topics">
Topics:
<a href="#" class="selected">All</a>
<a href="#" class="selected">Community</a>
<a href="#" class="selected">Conferences</a>
<a href="#" class="selected">Documentation</a>
<a href="#" class="selected">Plugins</a>
<a href="#" class="selected">Releases</a>
</div>
<table id="news">
<thead>
<tr>
<th>Date</th>
<th>Headline</th>
<th>Author</th>
<th>Topic</th>
</tr>
</thead>
<tbody>
<tr>
<th colspan="4">2011</th>
</tr>
<tr>
<td>Apr 15</td>
<td>Jquery 1.6 Bea 1 Released</td>
<td>John Resig</td>
<td>Release</td>
</tr>
<tr>
<td>Apr 16</td>
<td>Jquery 1.7 Bea 1 Released</td>
<td>John Resig</td>
<td>Conferences</td>
</tr>
<tr>
<td>Apr 17</td>
<td>Jquery 1.8 Bea 1 Released</td>
<td>John Resig</td>
<td>Plugins</td>
</tr>
<tr>
<td>Apr 18</td>
<td>Jquery 1.9 Bea 1 Released</td>
<td>John Resig</td>
<td>Documentation</td>
</tr>
<tr>
<th colspan="4">2012</th>
</tr>
<tr>
<td>Apr 15</td>
<td>Jquery 1.6 Bea 11 Released</td>
<td>John Resig</td>
<td>Release</td>
</tr>
<tr>
<td>Apr 16</td>
<td>Jquery 1.7 Bea 11 Released</td>
<td>John Resig</td>
<td>Conferences</td>
</tr>
<tr>
<td>Apr 17</td>
<td>Jquery 1.8 Bea 11 Released</td>
<td>John Resig</td>
<td>Plugins</td>
</tr>
<tr>
<td>Apr 18</td>
<td>Jquery 1.9 Bea 11 Released</td>
<td>John Resig</td>
<td>Documentation</td>
</tr>
</tbody>
</table>
</body>
</html>
위 예제의 결과는 그림 1과 같습니다.
위 결과에서 상단 Topic들 중, 하나를 선택 시 목록에서 Topic에 해당하는 것을 찾는 로직을 구현하는script를 작성하는 예제를 사용하여, selector를 이용하는 것과 traversing 메소드를 이용한 코드를 작성한 후, 비교합니다.
먼저 selector를 이용한 코드 :
$(document).ready(function(){
$('#topics a').click(function(event){
event.preventDefault();
$('#topics a.selected').removeClass('selected');
$(this).addClass('selected');
var topic = $(this).text();
console.log(topic);
$('#news tr').show();
if(topic != 'All'){
$('#news tr:has(td):not(:contains("'+ topic + '"))').hide();
}
});
});
코드설명 :
- 2라인 : Topic 들 중 하나를 클릭 시, 이벤트를 받는다.
- 3라인 : 현재 클릭한 위치에서 href로 가는 이벤트를 차단한다.
- 4라인 : 모든 topic에서 selected class를 모두 제거한다.
- 5라인 : 클릭한 자신의 topic은 selected를 추가한다.
- 7라인 : 클릭한 topic의 텍스트 값을 조회한다.
- 9라인 : News에 있는 tr를 모두 display 한다
- 10라인 : 클릭한 값이 All이 아닐 경우,
- jQuery에서는 Sizzle.select 메소드를 통해, selector 부분을 파싱하여, Expr.find["ID"]를 통해 '#news'을 검색하고, Expr.find["TAG"] 메쏘드에서 "tr"를 검색한다.
- 그 후, pseudos의 "has", "not", contains를 수행하여, topic을 포함하지 않는 것을 모두 숨긴다.
DOM 탐색 메소드를 이용한 코드 :
$(document).ready(function(){
$('#topics a').click(function(event){
event.preventDefault();
$('#topics a.selected').removeClass('selected');
$(this).addClass('selected');
var topic = $(this).text();
console.log(topic);
$('#news tr').show();
if(topic != 'All'){
$('#news').find('tr:has(td)').not(function(){
return $(this).children(':nth-child(4)').text() == topic;
}).hide();
}
});
});
DOM traversal 메소드를 사용하여 복잡한 selector 표현식을 수정하였습니다.
.find와 .not 를 사용하여 selector를 분리하였고, .not 함수는 call back 함수를 가지며, 그 결과가 false인 경우, hide 시킨다. 여기서는 jQuery의 확장 메소드( find, not, contains)를 사용하였습니다.
결과:
traversal 메소드는 메소드 실행결과를 pushStack에 저장하기 때문에, 전 메쏘드로 이동 시(.end() 사용), 그 결과를 다시 사용할 수 있도록 지원합니다. selector는 검색문장을 파싱하여, pseudos 메쏘드를 수행시켜 동작합니다. 둘 차이는 검색 진입점의 차이가 있을 뿐, 내부적으로 동작하는 것은 같은 함수를 사용합니다.
Selectors와 Traversal methods 중 어느 것을 사용하는 것이 좋은가?
Selector 또는 Traversal method는 서로 다른 표현식을 사용할 뿐이지 대부분 같은 결과를 얻을 수 있습니다. 그러나, 진입방식에 따라서, 같은 결과를 얻는 데 소요되는 시간이 다르게 됩니다. 또한 이 것은 브라우저 마다 성능이 차이가 발생합니다. 따라서, 타겟으로 하는 브라우저에서의 성능과 엘레멘트 검색방식의 효율성에 따라, 적절한 것을 사용하는 것이 좋은 방법이라 생각됩니다.
JQuery 검색 성능을 개선하기 위한 방법은 크게 2가지가 있습니다.
chaining를 사용해 성능 개선하는 방법
jQuery object instance는 전 object를 찾아가기 위해, .prevObject property를 가지고 있습니다. 이 것은 linked list 구조를 가지고 있으며, stack를 사용하여 구현되어 있습니다.
$(document).ready(function(){
$('#news tr').filter(function(index){
return (index % 3) == 1;
}).addClass('alt').end()
.filter(function(index){
return (index % 3) == 2;
}).addClass('alt2');
});
코드 설명 :
- $(‘#news tr’) : 도큐멘트에서 id가 news이고 tr를 가진 엘레먼트를 찾는다.
- 이때, jQuery object내 stack에 DOM 엘레멘트를 넣게 된다.
- .filter는 stack에 테이블 rows를 넣게 된다.
- .addClass는 그 rows들에 class 속성을 추가한다.
- .end() 메소드는 stack에 들어있는 전 element set를 꺼내게 된다.
- 그 뒤, .filer는 ‘#news tr’의 결과 element를 기반으로 테이블 rows를 찾게 되며, 그 결과 element set를 다시 stack에 넣게 된다.
- .addClass는 그 rows들에 class 속성을 추가하게 된다.
- 여기서, .end()가 stack에 쌓여 있는 것을 .filter 까지 꺼내는 역할을 통해, $(‘#news tr’)의 결과를 재사용함으로써, 문서 전체를 다시 뒤지는 비용을 줄이게 됨에 따라, 성능 향상을 가져옵니다.
결과 :
캐쉬를 가지고 성능 개선하는 방법
$(document).ready(function(){
$news = $('#news tr');
$news.filter(function(index){
return (index % 3) == 1;
}).addClass('alt');
$news.filter(function(index){
return (index % 3) == 2;
}).addClass('alt2');
});
코드 설명:
- $(‘#news tr’)결과를 $news에 저장해 둔다.
- 저장된 $news에서 filter로 추출된 것에 class 속성을 추가한다.
- 기존에 저장된 $news를 가지고, 다른 filter를 적용하여 class 속성을 추가한다.
여기서, $news = $(‘#news tr’);을 통해 news tr 결과를 $news에 저장해 둡니다. 저장해둔 $news를 각각의 filter에서 활용함에 따라, news tr을 문서에서 다시 찾는 비용을 줄였습니다. 이때, 캐쉬를 만들어 사용한 효과를 볼 수 있습니다.
References :
[1] http://jsperf.com/selecting-the-first#run / 스크립트 성능테스트 사이트.
[2] http://www.nextree.co.kr/p9889/
[3] Learning jQuery Fourth Edition