jQuery 에서는 CSS 셀렉터 와 엘리먼트를 선택해 처리하는 방법을 사용합니다.

그러나 이런 CSS 셀렉터 사용법을 좀더 잘 사용하면 처리속도의 향상을 가져옵니다.


여기에서 jQuery의 내부 처리와 같은 코드를 보고, jQuery를 빠르게 사용하기 위한 TIP 5 를

소개하고자 합니다.


1. 여러번 같은 실렉터를 실행하지 않는다.

2. 클래스만으로 지정하는 것을 금지한다.

3. #id 의 사용을 적극 권장한다.

4. 도중까지 결과를 재이용 한다.

5. 자식 셀렉터(>)를 잘 사용하면 처리가 빨라진다.


1. 여러번 같은 실렉터를 실행하지 않는다.

ㅁ 개선전

 // 예제1 
 $("div.foo").addClass("bar");
 $("div.foo").css("background","#ffffff");
 $("div.foo").click(function(){ alert('foo'); } );

ㅁ 문제점

jQuery는 CSS 셀렉터를 쓸 때마다 DOM에서 셀럭터와 일치하는 엘리먼트를 찾습니다.

$("div.foo") 실행하면 뒤에서 jQuery는 아래과 같은 작업을 처리합니다.

 // 셀렉터로 선택한 결과를 저장하는 배열

 var ret = [];

 // div 태그 목록을 열거
 var elems = document.getElementsByTagName("div");

 // 각각 클래스명이 foo 의 것을 ret 에 추가
 for(var i = 0; i < elems.length; i++){
    var classes = elems[i].className.split(" ");
    if(classes.indexOf("foo") != -1){
        ret.push(elems[i]);
    }
 }


  HTML 안에 포함됨 div 태그를 열거하고, 각 클래스명을 조사해 가는 것입니다.
 (Array.indexOf (은)는 비표준입니다만, 간단하게 쓰기 위해서 사용하고 있습니다)

  즉, 모두의 코드와 같이 $("div.foo") (을)를 3회 써 버리면 위에서처럼 처리가 3회 실행되어 버립니다. 비효율적이지요.

ㅁ 개선방법1: 캐쉬
  셀렉터의 실행 결과를 변수에 캐쉬해 둡니다. 2회 분의 $("div.foo") 실행시간을 단축시킬 수 있습니다.
 // 코드 1-1
 var foos = $("div.foo");
 foos.addClass("bar");
 foos.css("background", "#ffffff");
 foos.click(function(){alert('foo');});

ㅁ 개선방법2: 메소드 체인
  메소드 체인을 사용하면, jQuery 같아지고 처리 효율도 오릅니다.
 // 코드 1-2
 $("div.foo")
    .addClass("bar")
    .css("background", "#ffffff")
    .click(function(){alert('foo');});

$("div.foo")실렉터의 실행 결과가 다음의 메소드에 차례로 인계됩니다. 임시 변수를 필요로 하지 않는 것도 장점입니다.


2. 클래스만을 지정하는 것은 금지

ㅁ 개선전
 // 예제 2
 $(".foo").css("display", "none");

ㅁ 문제점
  클래스명 만을 지정하면, jQuery는 모든 HTML 노드를 열거하고, 각 클래스명을 확인합니다.

$(".foo") 의 배후는 다음과 같은 처리가 실행됩니다.
 // 셀렉터로 선택한 결과를 저장할 배열
 var ret = [];

 // 모든 태그를 열거한다
 var elems = document.getElementsByTagName("*");

 // 각 클래스명이 foo 의 것을 ret 에 넣는다
 for(var i = 0; i < elems.length; i++){
     var classes = elems[i].className.split(" ");
     if(classes.indexOf("foo") != -1){
        ret.push(elems[i]);
     }
 }

  모든 태그를 열거하여 루프를 돌리기 때문에 비효율적입니다.
  조금, 이야기가 빗나가지만 Firefox3 나 Opera9.5, Safari3 에는 getElementsByClassName() 메소드가 기본으로 포함되어 있습니다. 그 때문에 이러한브라우저는 빠른 $(".foo") 를 실행할 수 있는 능력을 가지고 있습니다. 그러나, jQuery 1.2.6 의 시점에서는 기본 getElementsByClassName()을 사용하고 있지 않습니다.

  jQuery 의 차기 CSS 셀렉터인 Sizzle는 getElementsByClassName()가 정의되는 경우에 사용하도록 구현되어있는 것 같습니다.

ㅁ 개선방법: 태그를 명시 한다
  태그를 명시합니다.
 $("div.foo").css("display", "none");

  모든 노드로부터가 아닌,  지정된 태그중에서 클래스명으로 검색이 좁히게 되어, 루프의 회수가 큰폭으로 줄어듭니다.


3. #id 를 적극적으로 사용한다

ㅁ개선전
 <body>
   <script src="jquery.js"></script>
   <script>
     $(function(){
       $(".main").css("color", "red");  //문제 부분
     });
   </script>
   <div class="main">
   < ... >
   </div>
 </body>

ㅁ 문제점
  반복적으로 언급해온 대로 jQuery는 클래스 이름으로 검색하는 것은 비효율적이다.

  HTML 설계의 이야기가 되어 버립니다만, HTML 중 1번만 등장하지 않는 클래스명은 id 화 하는게 좋습니다. 그 편이 JavaScript 에서 취급하기에도 형편상 좋습니다.

ㅁ 개선방법
  main 을 클래스가 아닌 id 로 변경합니다.
 <body>
   <script src="jquery.js"></script>
   <script>
     $(function(){
       $("#main").css("color", "red"); // 해결부분
     });
   </script>
   <div id="main">
     < ... >
   </div>
 </body>
  jQuery는 셀렉터에 id 가 지정되어 있을 경우에는 재귀적으로 탐색하지 않고 getElementById() 를 이용합니다. 따라서 모든 노드를 열거하는데 비해 비교적 빠른 처리를 할 수 있습니다.

4. 도중까지의 결과를 재이용한다

ㅁ 개선전
 <body>
  <script src="jquery.js"></script>
  <script>
    $(function(){
      $("#main div.entry").css( ... );
      $("#main div.entry div.body")  // 문제부분
          .css( ... );
    });
  </script>
  <div id="main">
    <div class="entry">
      <div class="header"> ... </div>
      <div class="body"> ... </div>
    </div>
    <div class="entry">
      <div class="header"> ... </div>
      <div class="body"> ... </div>
    </div>
  </div>
 </body>

ㅁ 문제점
아래를 읽어 보면 이 경우에 대해 알수 있습니다.
  $("#main div.entry")              // (1)
  $("#main div.entry div.body")     // (2)

 (2) 의 셀렉터에서는,
   1. #main 을 탐색
   2. 그 자손으로부터 div.entry 를 열거
   3. 그 자손으로부터 div.body 를 열거
 
  는 작업을 실행합니다. 이 중 1~2는 (1)(과)과 완전히 같은 처리입니다. (1)에서 구한 결과를 다시 사용하면 처리속도가 향상할 것입니다.

ㅁ 개선방법 : 캐쉬 전략
  var entries = $("#main div.entry").css( ... );
  $("div.body", entries).css( ... );

  $() 함수의 제2 인수에는 CSS 셀렉터를 찾는 기점을 지정할 수 있습니다. (1)의 결과에 포함된 엘리먼트의 자식에서 div.body 를 찾아 줍니다.

find() 메소드를 사용해도 괜찮을 것입니다.
  var entries = $("#main div.entry").css( ... );
  entries.find("div.body").css( ... );

어라?  여기까지 오면  메소드 체인이 생길 것 같네요.
 $("#main div.entry").css( ... )
     .find("div.body").css( ... );


ㅁ 응용 사례
  div.head 도 찾고 싶은 경우에는 어떻게 하면 좋을 까요?

  네, 이렇게 하면 좋겠네요.
 var entries = $("#main div.entry").css( ... );
 entries.find("div.body").css( ... );
 entries.find("div.head").css( ... );

  이 녀석도 메소드 체인 해보죠. end()를 사용하면, find()로 찾기 이전 상태에 되돌릴 수 있습니다.
 $("#main div.entry")
     .css( ... );
     .find("div.body") // #main div.entry div.body 가 된다
         .css( ... )
     .end()            // #main div.entry로 돌아온다
     .find("div.head") // #main div.entry div.head 가 된다
         .css( ... )
     .end();
 
   여기까지 오면 곡예수준입니다만.....윈래 코드보다 더 빠르다는것은 틀림없습니다.


5. 자식 셀렉터(>)를 사용하면 빨라질수 있다.

ㅁ 개선전

 <body>
   <script src="jquery.js"></script>
   <script>
     $(function(){
        $("#main div.entry").css( ... ); // ← 코코
     });
   </script>
   <div id="main">
     <div class="entry">
       <div class="header"> ... </div>
       <div class="body"> ... </div>
     </div>
     <div class="entry">
       <div class="header"> ... </div>
       <div class="body"> ... </div>
     </div>
   </div>
 </body>

ㅁ 문제점
 ' #main div.entry '는 #main 의 후에 자손 실렉터(스페이스)가 있습니다. 즉, #main 노드아래의 모든 div 노드로부터 entry 클래스를 찾아냅니다.

$("#main div.entry") 의 배후에서는 다음과 같은 처리가 실행되고 있습니다.
 // 셀렉터로 선택한 결과를 저장하는 배열
 var ret = [];
 
 // #main을 찾는다
 var main = document.getElementsById("main");

 // #main의 아래에서 전체 div 를 열거한다
 var elems = main.getElementsByTagName("div");

 // 각 클래스명이 foo 의 것을 ret 에 넣는다
 for(var i = 0; i < elems.length; i++){
     var classes = elems[i].className.split(" ");
     if(classes.indexOf("foo") != -1){
         ret.push(elems[i]);
     }
 }

  elems 에는 div#main 의 아래의 모든 div 태그가 저장됩니다. 이 모두에 대한 클래스명을 확인하는 것이, 경우에 따라서는 늦어져 버립니다.

  만약,div.entry 가  div#main 아래에만 존재한다면, "자손 실렉터"는 아니고 "자식 셀렉터"를 사용하면 효율적으로 동작할지도 모릅니다.

ㅁ개선방법
  자식 셀렉터(>)를 사용합니다.
    $("#main > div.entry").css( ... );

jQuery 그럼 자식 셀렉터가 나오면, 모든 자손이 아니고, 자식중에서 매치하는 것을 조사합니다. 손자와 그 자식에 대해서는 조사하지 않기 때문에, 고속화가 기대됩니다.

$("#main > div.entry") 의 배후에서는 다음과 같은 처리가 실행됩니다.
 // 셀렉터로 선택한 결과를 저장하는 배열
 var ret = [];

 // #main 를 찾는다
 var main = document.getElementsById("main");

 // #main 자식 노드중에서
 // 태그 이름이 DIV이고, 클래스명이 entry 의 것을 ret에 넣는다
 var child = main.firstChild;
 while(child){
     var classes = elems[i].className.split(" ");
     if (child.tagName == "DIV"
     && classes.indexOf("entry") != -1){
         ret.push(child);
     }

     child = child.nextSibling;
 }
꼭 자식 셀렉터를 사용하면 반드시 빨리된다 싶지 않지만 자식의 수에 비해 자손이 많은 경우, 자식셀럭터 쪽이 빨라집니다.

ㅁ 실제 코드 시험
  실제로 브라우저로 실행했을 때에 CSS 실렉터에 의해서 처리 속도가 얼마나 개선하는지를 확인해 봅시다.

시험은 블로그의 HTML 에서 실행해 보았습니다. HTML 구조는 다음과 같습니다.
 <div id="days">
    <div class="day">
        <h2>2008년12월11일</h2>
        <div class="body">
            <div class="section">
                <h3>타이틀</h3>
                <p>본문</p>
            </div>
        </div>
    </div>
    <div class="day"> ... </div>
    <div class="day"> ... </div>
 </div>

이러한 HTML 에 대해서,jQuery (을)를 실행해 보았습니다.
 CSS 셀렉터
 FireFox 2
 IE7  Opera9  Safari(win)
 .body  22.18ms  19.85.ms 5.32ms
2.49ms
 div.body  2.34.ms  2.82.ms 1.24ms
 0.49ms
 #days >div.day>div.body  2.66.mx  1.72ms 1.25ms
 0.44ms
    * 모든 브라우저로,.body 에 비해 div.body (분)편이5~10배속구 되어 있다.
    * #days > div.day > div.body 는 아이 셀렉터를 2회 사용하고 있는데,
      div.body 와 같을 정도의 속도로 실행 되어 있다.


마지막에

  jQuery 는 라이브러리인 이상, DOM 을 직접 접근하는데 비해 늦어지는 것은 피할 수 없습니다.
  아무래도 처리 속도가 신경이 쓰이는 경우는, jQuery 의 코드를 DOM 를 직접 컨트롤하는 코드로 변환하면 좋을 것입니다. 경험적으로 Firefox 나 IE 그리고 처리 속도가10배정도가 됩니다.
  다만, 개발 효율설 측면에서는 처음에 jQuery 를 사용해 쓰기 시작하는 것을추천합니다.
jQuery 의 코드를 DOM 직접 변환하는 것은 간단합니다만, DOM 직접으로 개발을 진행시키는 것은 귀찮지요.
2009/07/16 17:06 2009/07/16 17:06

Trackback Address :: 이 글에는 트랙백을 보낼 수 없습니다