Program/Javascript

제이쿼리 튜닝, 퍼포먼스 향상(Tips And Tricks)

Hue Kim 2013. 9. 9. 15:54

이 글은 jQuery Proven Performance Tips And Tricks 를 보고 공부한 점을 정리한 글입니다. 원문의 내용과 똑같은 부분도 있고 제가 알고 있는 내용을 따로 덧붙인 부분도 있으니 원문을 꼭 보시기 바랍니다.

——————————————————————————————————————–

jQuery Proven Performance Tips And Tricks

퍼포먼스 향상 패턴을 사용하는 것이 왜 중요한가요?

  • 프로그램 코드는 간단 명료해야지 엉성하게 만들면 안됩니다.
  • 좋은 코딩 습관은 문제를 해결하는데 적합한 방법을 제공합니다.
  • 퍼포먼스에 좋은 패턴을 사용하지 않고 코딩을 한다면 브라우저가 그 만큼 일을 더 많이 하게 됩니다.
더 많은 일 = 더 많은 메모리 사용 = 느린 프로그램

..

퍼포먼스 향상 Tip 1

항상 최신 버전의 jQuery를 사용하세요

  • 가능하다면 항상 최신 버전의 jQuery를 사용하세요
  • 최신 버전은 이전 버전에 비해 성능이 향상되고 버그가 수정된 경우가 많습니다.
  • jQuery 파일을 최신 버전으로 교체해도 문제가 생기는 경우는 많지 않은편입니다.

퍼포먼스 향상 Tip 2

셀렉터를 어떻게 사용하느냐에 따라 성능이 향상됩니다.

  • 모든 셀렉터가 같은 방법으로 jQuery 객체를 생성하는 것은 아닙니다.
  • jQuery는 셀렉터를 사용하는 다양한 방법을 제공하지만, 각 방법별로 내부적 처리가 다르기 때문에 어떤 것은 빠르고 어떤 것은 느립니다.
  • jQuery는 DOM 노드를 탐색할 때, 가능하면 브라우저 내장 메서드를 사용해서 탐색해서 속도를 향상시키지만 브라우저 내장 메서드가 지원이 안되는 경우 모든 DOM 노드를 순회해야 하기 때문에 느려집니다.
ID & Element 셀렉터가 가장 빠릅니다.
  • $(‘#Element, form, input’)
  • 이 셀렉터들은 내부적으로 브라우저 내장 메서드인 getElementById(), getElementsByTagName()을 사용하기 때문에 가장 빠릅니다.
  • 꼭 셀렉터의 경우가 아니더라도 코딩을 할 때 가능한 경우 브라우저 내장 메서드를 사용하는 것이 가장 빠릅니다.
Class 셀렉터는 느립니다.
  • $(‘.element’)
  • 클래스를 선택할 수 있는 브라우저 내장 메서드인 getElementsByClassName()은 IE5~8에서는 지원되지 않습니다.
  • 그래서 클래스 셀렉터를 사용하면 IE5~8에서는 느립니다. 브라우저 내장 메서드 대신 jQuery가 직접(모든 DOM 노드 순회) 클래스를 찾기 때문입니다.
Pseudo & Attribute 셀렉터는 가장 느립니다.
  • $(‘:visible, :hidden’)
  • $(‘[attribute=value]‘)
  • 이 셀렉터는 브라우저의 내장 메서드가 없기 때문에 jQuery가 직접 찾아야 해서 느립니다.
  • 최신 브라우저들 중 CSS 셀렉터로 DOM 탐색이 가능한 querySelector(), querySelectorAll() 을 지원하는 브라우저에서는 빠릅니다.
  • Pseudo & Attribute 는 매우 편리하지만 가능 느릴수 도 있기 때문에 사용에 주의를 기울여야 합니다.

퍼포먼스 향상 Tip 3

Parent and Children 셀렉터를 사용할 때 좋은 패턴에 대해 알아봅니다. 아래 패턴에서 $parent는 $(‘#parent’) 를 담아놓은 변수입니다. $(‘#parent’) 을 미리 처리했으므로 여기에 수행되는 시간은 고려하지 않는다는 의미입니다.

  1. $(‘.child’, $parent).show();    // context
  2. $parent.find(‘.child’).show();    // find()
  3. $parent.children(‘.child’).show();    // immediate children
  4. $(‘#parent > .child’).show();    // child combinator selector
  5. $(‘#parent .child’).show();    // class selector
  6. $(‘.child’, $(‘#parent’)).show();    // created context
1) $(‘.child’, $parent).show();
  • 이 패턴은 내부적으로 $parent.find(‘.child’).show(); 으로 변환되어 처리됩니다.
  • 가장 빠른 방법에 비해 ~5-10% 느립니다.
2) $parent.find(‘.child’).show();
  • 이 방법이 가장 빠릅니다.
  • 여기에 대한 설명은 곧 하겠습니다.
3) $parent.children(‘.child’).show();
  • 이 방법은 내부적으로 $.sibling 과 자바스크립트의 nextSibling() 메서드를 사용합니다.
  • 가장 빠른 방법에 비해 ~50% 느립니다.
4) $(‘#parent > .child’).show();
  • jQuery는 내부적으로 Sizzle 이라는 CSS 셀렉터 엔진을 사용하는데 Sizzle은 셀렉터를 오른쪽에서 왼쪽으로 읽어서 사용합니다.
  • 즉, .child 를 먼저 다 찾고 그 중에서 부모가 #parent 를 가진 것을 찾는 식입니다.
  • 가장 빠른 방법에 비해 ~70% 느립니다.
5) $(‘#parent .child’).show();
  • 4번과 마찬가지로 .child를 찾고 #parent를 찾습니다.
  • 내부적으로 .find() 메서드도 사용합니다.
  • 가장 빠른 방법에 비해 ~77% 느립니다.
6) $(‘.child’, $(‘#parent’)).show();
  • 내부적으로 $(‘#parent’).find(‘.child’) 로 동작합니다.
  • 2번 가장 빠른 방법과 비슷하게 생겼지만 2번 방법은 미리 $(‘#parent’)를 계산해 놓은 $parent 변수를 사용하고 있어서 더 빠릅니다.
  • 가장 빠른 방법에 비해 ~23% 느립니다.
가장 빠른 방법은 2) $parent.find(‘.child’).show(); 입니다. $parent 는 $(‘#parent’) 를 변수에 담아놓은 것입니다. 이런 패턴을 캐싱이라고 합니다. 즉, 자주 사용할 jQuery객체를 변수에 담아놓아서 나중에 사용할 때 다시 jQuery 객체를 만드는데 사용할 시간을 절약하는 것입니다.
무엇보다 getElementById 와 같은 브라우저 내장 메서드를 사용하는 것이 가장 빠릅니다. 가능하다면 이 것을 사용하세요
위에서 .find() 메서드를 많이 사용하고 있는데 .find() 메서드는 위에서 아래로 재귀적으로 탐색을 한다는 사실도 알아두세요

퍼포먼스 향상 Tip 4

꼭 필요한 경우가 아니라면 jQuery를 사용하지 마세요

  • $() 를 한번 수행할 때마다 당연히 시간이 소요됩니다. 필요하지 않은 경우라면 안쓰는 것이 성능향상에 좋습니다. jQuery 대신 “보통의” 자바스크립트 코드를 사용하세요
  • jQuery로 작성된 소스코드에 보통 자바스크립트 코드를 사용하면 안될 것 같은 느낌이 드신다면 전혀 걱정하지 않으셔도 됩니다. jQuery도 그냥 자바스크립트로 만들어진 함수입니다. jQuery와 보통 자바스크립트를 섞어쓰셔도 아무 문제가 없습니다.
  1. $('a').bind('click', function () {
  2. console.log('You clicked: ' + $(this).attr('id'));
  3. });

이렇게 작성하시지 마시고

  1. $('a').bind('click', function () {
  2. console.log('You clicked: ' + this.id);
  3. });

이렇게 작성하세요. function () {} 안에서 사용할 수 있는 this 는 a 태그가 만든 DOM 객체입니다. 여기에서 id 어트리뷰트에 접근하기 위해 $(this).attr(‘id’); 를 할 필요는 없습니다. 그냥 this.id 와 같이 DOM 객체 – 프로퍼티 접근방법을 사용하시면 되고, 더 빠릅니다.

퍼포먼스 향상 Tip 5

캐싱(Caching) 을 사용하세요. 위에서도 잠깐 말씀드렸지만 캐싱은 단지 나중에 다시 사용할 것 같은 “좀 시간이 걸렸던 작업” 을 변수에 저장해서 나중에 사용할 때는 그 시간이 걸렸던 작업을 다시 안해도 되게 해주는 패턴입니다.

이 글에서 말하는 캐싱은 셀렉터를 사용해 jQuery 객체를 만드는 작업을 캐싱하는 것입니다.

  1. var parents = $('.parents'), // caching
  2. children = $('.parents').find('.child'), // bad
  3. kids = parents.find('.child'); // good

위 코드에서 children 변수는 캐싱해놓은 parents 변수를 사용하지 않고 $(‘.parents’)를 또 만들었습니다. kids 변수는 캐싱해놓은 parents 변수를 사용하고 그래서 children 보다 빠릅니다.

  1. var foo = $('.item').bind('click', function () {
  2. foo.not(this).addClass('bar')
  3. .removeClass('foobar')
  4. .fadeOut(500);
  5. });

위 코드는 $(‘.item’) 을 foo 라는 변수에 캐싱하고 있습니다. 그리고 $(‘.item’) 에 click 이벤트 핸들러로 등록해 놓은 함수안에서 foo 변수를 사용하고 있습니다. item 이라는 클래스를 가진 엘리먼트들이 클릭되었을 때 자기 자신만 빼고 나머지 item 들에 대해 addClass, removeClass, fadeOut 하는 코드입니다. 캐싱을 통해 성능도 향상되고 소스코드도 더 간결해 보이는 것 같습니다.

퍼포먼스 향상 Tip 6

메서드 체이닝을 사용하세요

  1. var parents = $('.parents').doSomething().doSomethingElse();
  • 위 코드에서 doSomething() 메서드를 실행한 다음에 연달아서 doSomething() 메서드를 실행하고 있습니다. 이 것을 메서드 체이닝 또는 그냥 체이닝이라고 합니다.
  • $(‘.parents’).doSomething(); $(‘.parents’).doSomethingElse(); 이렇게 두 번 한것과 같은 실행을 하면서 속도는 더 빠릅니다.
  • 대부분의 jQuery 메서드들은 체이닝을 지원합니다. 체이닝이 가능해지는 원리는 간단합니다. $(‘.parents’).doSomething(); 의 실행결과 리턴값이 $(‘.parents’) 이기 때문에 거기에 연달아서 .을 찍고 doSomethingElse(); 를 실행해도 되는 것입니다.
  • 코드도 짧아지고 가독성도 높아집니다. 아래의 코드처럼 엔터를 몇 번 쳐주면 가독성은 더 좋아집니다.
  1. // Without chaining
  2. $('#notification').fadeIn('slow');
  3. $('#notification').addClass('.activeNotification');
  4. $('#notification').css('marginLeft', '50px');
  5.  
  6. // With chaining
  7. $('#notification').fadeIn('slow')
  8. .addClass('.activeNotification')
  9. .css('marginLeft', '50px');

퍼포먼스 향상 Tip 7

이벤트 위임(event delegation)을 사용하세요 이벤트 위임은 이벤트의 bubble 속성을 사용한 것입니다. A 엘리먼트에 이벤트 핸들러를 등록하고 싶을 때 그 엘리먼트에 바로 붙이지 않고 그보다 상위 엘리먼트에 B에 등록합니다. 이벤트가 A에서 발생했어도 B로 bubble 되어 올라가는데 이때 B에 등록된 핸들러에서 A에서 발생한 것인지 살펴보고 맞으면 이벤트 핸들러를 실행하는 방식이 이벤트 위임입니다.

몇몇 엘리먼트에 이벤트 핸들러를 등록할 때는 이벤트 위임이 별로 효과가 없습니다. 하지만 어떤 ul에 li가 100개 있는데 li에 click 이벤트 핸들러를 등록하는 경우가 있다면, li 하나하나마다 등록해서 이벤트 핸들러 100개를 만드는 것보다 ul에 이벤트 핸들러 하나를 등록하는 것이 성능면에서 좋을 것입니다.

jQuery에서 엘리먼트에 이벤트 핸들러를 등록하는데 사용되는 메서드는 엘리먼트에 직접 등록하는 .bind() 메서드, 이벤트 위임을 사용하는 .live() 메서드, .delegate() 메서드가 있습니다. 이때 .live() 보다는 .delegate() 가 성능면에서 더 좋습니다.

최근에 나온 jQuery 1.7 버전에는 이벤트를 등록할 수 있는 새로운 메서드인 .on() 메서드가 나왔습니다. 그리고 .bind(), .live(), .delegate() 메서드 모두 jQuery 내부적으로는 .on() 메서드를 사용하게 소스코드가 바뀌었습니다. 그래서 jQuery 1.7 이상 버전을 사용하신다면 여러 메서드들 중 하나를 선택하실 필요없이 그냥 .on() 메서드를 사용하시면 됩니다.

퍼포먼스 향상 Tip 8

DOM 에 노드를 추가하거나 삭제하는 작업은 최소화하는 것이 좋습니다.

  • DOM 접근은 (성능면에서) 비용이 많이 드는 작업입니다.
  • 이 것은 .append(), .insertBefore(), .insertAfter() 등의 메서드 사용을 최소화 하면 성능이 향상된다는 것을 의미합니다.
  • 정보의 저장/조회 용도라면 .text(), .html() 보다 .data() 를 사용하는 것이 좋습니다.

Tip 1: 더 나은 .append() 사용법

  • .append() 는 한 번만 사용할 수 있는 방법을 찾으십시오
  • append를 여러 번 하는 것은 한 번 사용하는 것보다 90% 까지 느려질 수 있습니다. 그리고 캐싱되지 않은 jQuery 객체에 append 한다면 추가로 20%까지 느려집니다.

Tip 2: .detach() 를 사용하세요

  • .detach() 는 노드를 DOM에서 제거하면서 캐싱합니다. 그래서 나중에 다시 DOM에 넣을 수 있습니다.
  • 이벤트 핸들러가 많이 걸려있는 노드를 DOM에서 제거했다가 나중에 다시 넣는다면, 이벤트 핸들러를 다시 등록해줘야 할텐데 .detach() 를 사용하면 그냥 다시 DOM에 넣기만 하면 됩니다. .detach() 로 DOM에서 삭제하면 노드에 붙은 이벤트 핸들러 정보를 그대로 가지고 있기 때문입니다.
  • .detach() 를 사용하지 않은 경우보다 60%까지 빠릅니다.
  1. // .detach() example
  2. $('p').click(function () {
  3. $(this).toggleClass('off');
  4. });
  5.  
  6. var p;
  7. $('button').click(function () {
  8. if (p) {
  9. /* ..additional modification */
  10. p.appendTo('body');
  11. p = null;
  12. } else {
  13. p = $('p').detach();
  14. }
  15. });

Tip 3: 더 나은 .data() 사용법

  • 보통 .data() 메서드는 $(‘#elem’).data(key, value); 와 같은 형태로 사용합니다.
  • 이보다는 $.data(elem, key, value); 처럼 사용하는 것이 더 빠릅니다. 여기서 첫 번째 인자인 elem은 var elem = document.getElementById(‘#elem’); 과 같이 만들어진 DOM 객체를 의미합니다.
  • (jQuery 객체).data(..) 형태로 사용하는 것보다 $.data(DOM객체, ..) 형태로 사용하는 것이 더 빠르다는 의미입니다.
  • 위에서 보신 것처럼 var elem; 을 DOM 객체로 만드는 수고가 한 번 더 들지만, 만약 미리 만들어 놓은 상황이라면 $.data() 를 사용하는 것이 더 빠릅니다.

퍼포먼스 향상 Tip 9

loop를 잘 사용하세요 – 반복문, jQuery 에서는 $.each() 와 .each()

  • jQuery의 $.each() 와 .each() 보다 자바스크립트 본래의 for, while 문이 더 빠르다는 것을 알고 계신가요?
  • jQuery의 loop는 편리하지만 항상 최고의 성능을 발휘하는 것은 아닙니다.
  • 그리고 꼭 jQuery의 loop 가 아니더라도 프로그래밍을 할 때 가능하다면 반복문은 피하는 것이 좋습니다. loop는 느립니다.

퍼포먼스 향상 Tip 10

꼭 필요한 경우가 아니라면 jQuery 객체를 만들지 마세요

  • 개발자들은 대체 가능한 더 간단한 방법이 있는 경우에도 jQuery 객체를 만들어 쓰곤 합니다. Tip 4 에서 살펴봤던 것처럼 $(this).attr(‘id’) 보다는 this.id 가 더 빠릅니다.
  • $.method() 형태가 $.fn.method() 보다 빠릅니다. Tip 8 에서 살펴본 것처럼 $.data() 가 .data() 보다 빠릅니다. 그래서 가능하다면 이렇게 사용하면 좋습니다.
  • 하지만 $.data() 를 사용하기 위해서는 역시 Tip 8 에서 살펴본 것처럼 DOM 객체를 미리 만들어 놔야 하므로, 상황에 따라 가능할 경우 그렇게 하면 됩니다.

보너스 Tip

DRY 하세요~ Don’t Repeat Yourself – 같은 코드를 반복적으로 사용하지 않는 방법을 연구하면 좋습니다.

간단한 예제 소스를 보겠습니다.

  1. // Let's store some default values to be read later
  2. var defaultSettings = {};
  3. defaultSettings['carModel'] = 'Mercedes';
  4. defaultSettings['carYeasr'] = 2012;
  5. defaultSettings['carMiles'] = 5000;
  6. defaultSettings['carTint'] = 'Metallic Blue';
  7.  
  8. // Non-DRY code
  9. $('.someCheckbox').click(function () {
  10. if (this.checked) {
  11. $('#input_carModel').val(defaultSettings.carModel);
  12. $('#input_carYear').val(defaultSettings.carYear);
  13. $('#input_carMiles').val(defaultSettings.carMiles);
  14. $('#input_carTint').val(defaultSettings.carTint);
  15. } else {
  16. $('#input_carModel').val('');
  17. $('#input_carYear').val('');
  18. $('#input_carMiles').val('');
  19. $('#input_carTint').val('');
  20. }
  21. });
  22.  
  23. // DRY code
  24. var props = ['carModel', 'carYear', 'carMiles', 'carTint'];
  25.  
  26. $('.someCheckbox').click(function () {
  27. var checked = this.checked;
  28.  
  29. /*
  30. Non-DRY code 에서는 무엇을 반복하고 있나요?
  31. 1. input_ .. 형태로 생긴 field 명 들
  32. 2. 값을 설정하기 위한 $('#input_ .. ').val(defaultSettings. .. );
  33. 3. 값을 초기화하기 위한 $('#input_ .. ').val('');
  34.  
  35. DRY code 에서는 어떻게 바꿀 수 있나요?
  36. 1. input_ .. 형태로 생긴 field 명 들을 자동으로 생성
  37. 2. $.each 로 loop를 돌면서 key를 받아서 defaultSettings[key] 형태로 사용
  38. 3. 삼항연산자를 사용해서 - checked ? 설정 : 초기화 - 코드를 간략하게 만듬
  39. */
  40.  
  41. $.each(props, function (i, key) {
  42. $('#input_' + key).val(checked ? defaultSettings[key] : '');
  43. });
  44. });

..