Fogeaters, Light The World.

15

2017-Sep

[jQuery] 성능 최적화

작성자: title: MoonBlonix IP ADRESS: *.64.228.3 조회 수: 1859

출처 : http://yubylab.tistory.com/entry/jQuery-jQuery-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94


여전히 DOM을 다루는데 있어서는 jQuery는 최고의 라이브러리중 하나라고 생각을 합니다. 이전에 jQuery 제대로 사용하기라는 글을 썻습니다. 그때는 jQeruy의 Bad Practice 에 초점을 맞췄다면 이번에는 Performance 측면에서의 jQuery 코드작성법에 대해서 알아보겠습니다. 



1.Append Outside of Loops

jQuery가 DOM을 다루는데 개발자 입장에서는 매우 편합니다. 그런데 편한만큼 그 내부에서는 많은 작업들이 이뤄지고 있고 그 작업은 우리가 생각하는것 이상으로 비용이 꽤나 들어가는 작업입니다. 예를들면 이런 작업입니다. 


<ul id="list"></ul>

로 빈 리스트를 마크업해둔상태에서 Ajax 를 통해 DOM을 붙이는 작업이 있습니다. 코드를 한번 보겠습니다.



$.each( myArray, function( i, item ) {

    var newListItem = "<li>" + item + "</li>";

    $( "#list" ).append( newListItem );

});


전달받은 myArray를 each문을 돌면서 하나하나 ul#list에 붙이는 작업이니 일단 원하는 결과를 얻었습니다. 그런데 위의 경우에는 매번 ul#list에 하나하나 엘리먼트를 붙이는 작업입니다. 배열의 엘리먼트의 갯수가 많아는 경우에는 그 갯수만큼 DOM을 조작해야하는 비용적인 측면에서 매우 과한 소비가 일어나게 됩니다.


DOM조작은 될수있으면 최소화 하는 것을 지향해야합니다. 위의 코드는 다음과 같이 작성가능합니다.



var myHtml = "";

$.each( myArray, function( i, item ) {

    myHtml += "<li>" + item + "</li>";

});

$( "#ballers" ).html( myHtml );


2.Cache Length During Loops

정말 아무렇지 않게 다음과 같은 for 문을 작성합니다.



for(var i=0; i < element.length; i++){
     // Do something
}

뭐 일단 동작에는 아무런 문제가 없습니다. 그런데 위코드의 문제는 loop를 반복할때마다 엘리먼트의 lenght 프로퍼티에 계속 접근을 한다는 것입니다. 이뿐만아닙니다.  



var sec =0;
setInterval(function(){
     sec++;
     $('#timmer').html(sec);
},1000);

위와같은 코드에서 매번 초를 바꿔주는 코드를 업데이트 할때마다 timmer 엘리먼트를 찾게 됩니다. 

자주 반복해서 사용되는 엘리먼트는 브라우저상에 캐시를 시켜두고 그 캐시된 상태의 엘리먼트만를 통해 무언가 작업을 해야합니다. 위의 코드는 다음과 같이 작성하는 것을 권장합니다.



var myLength = myArray.length;
for ( var i = 0; i < myLength; i++ ) {
    // do stuff
}

var sec =0;
var timmer  = $('#timmer');
setInterval(function(){
     sec++;
     timmer.html(sec);
},1000);


3.Detach Elements to Work with Them

동적으로 테이블을 생성하는 작업을 한다고 가정하겠습니다. 기존에 테이블에 수많은 데이터가 존재하고 이제 Ajax 요청으로 새롭게 테이블을 구성해야 하는 상황입니다.

아마 대부분 이런식으로 작성을 할것같습니다.



var table = $('#table');
$.ajax({
     .....
     success: function(list){
          table.children().remove();
          table.append(...)     
     }
})

remove() 메서드를 사용해 내부의 자식 요소들을 모두 제거하고 거기에 새로운 데이터를 append 하는 방식입니다. 위에서도 말했다시피 우리는 최대한 느린 DOM을 조작하는 일을 최대한 피하려고 합니다. 그래서 위와 같이 DOM의 하위요소들을 지우고 나중에 다시 추가하려는 작업을 진행할때 사용하라고 jQuery1.4버전 이후부터 제공하는 Detach() 라는 메서드를 제공하고 있습니다.



var table = $('#table');
$.ajax({
     .....
     success: function(list){
          table.detach();
          table.append(...)     
     }
})

훨씬 좋은 성능으로 DOM을 다룰수가 있습니다.




4.Don't Act on Absent Elements

존재하지 않는 엘리먼트를 상대로 무언가를 시도하지 말라는 이야기입니다. 예를들면 이런거 입니다.



$('#popup').hide();
.. Do Something
$('#popup').show();

팝업을 띄우기 전에 일단 popup이라는 엘리먼트가 있다면 숨기고 새롭게 띄우려고 합니다. 그래서 띄우기 전에 일단 한번 hide()를 시키고 작업을 시작합니다.
hide() 메서드가 만약popup엘리먼트가 있다면 별로 문제될것이 없습니다. 그런데 popup이라는 엘리먼트가 동적으로 생성되기 때문에 DOM 상에 존재하지 않는다면 꽤많은 오버해드가 발생하게 됩니다. 실제 jQuery UI의 위젯들을 사용할때도 강력하게 권장하는 가이드이기도 합니다.
그럼 어떤식으로 작성을 하는지 보겠습니다.

위코드 보다 조금 더 좋은 방법입니다.


var popup = $('#popup');
if(popup.length){
     popup.hide();
}
일단 엘리먼트 존재 유무를 판단하고 동작을 시킵니다.

그러나 최상의 방법은 다음과 같습니다. 해당 동작을 통합해서 다루는 플러그인을 추가하는 것입니다.


jQuery.fn.popup = function( func){
     this.length && func.apply(this);
     return this;
}

$('#popup').popup(function(){
     ... Do Somthing
})

5.Optimize Selectors

jQuery의 가장 큰 장점은 DOM을 다룰때 엘리먼트 접근이 매우 편하다는 것입니다. 그냥 생각하는 그대로 접근을 해도 어지간하면 다 접근이 가능합니다.
대신 jQuery 가 제공하는 셀렉터들의 특성을 제대로 이해하지 못하고 사용하다 보면 성능상의 보틀넥을 발생시키는 요인이 될수있습니다.

1) jQuery Extensions

가능하다면 jQuery가 제공하는 jQuery extensions가 포한함 셀렉터의 사용을 피하는것이 좋습니다. 해당 메서드는 https://api.jquery.com/category/selectors/jquery-selector-extensions/ 여기에서 확인 가능합니다. extensions가 제공하는 메서드는 브라우저가 기본으로 제공하는 querySelectorAll() DOM 메서드의 성능상의 이점을 전혀 사용하지 못합니다. jQuery는 1.8버전을 시작으로 Sizzle 이라는 셀렉터 엔진을 사용하여 jQuery를 새롭게 제공하고 있습니다. 관련된 내용은 https://blog.jquery.com/2012/07/04/the-new-sizzle/ 이곳에서 확인하시면 됩니다.

// Slower (the zero-based :even selector is a jQuery extension)
$( "#my-table tr:even" );

// Better, though not exactly equivalent
$( "#my-table tr:nth-child(odd)" );
위에서 사용되는 :even 의 경우에는 CSS 가 제공하는 selectore요소가 아닙니다. 모든 경우는 아니지만 몇몇의 경우에는 성능상의 이슈를 만들기도 합니다.

2) Avoid Excessive Specificity

지나치게 구체적으로 DOM을 선택하는 것도 생각과는 다르게 성능상에 좋지 않은 이슈를 만들어 냅니다. 내가 자세하게 해당 엘리먼트를 지정해주면 jQuery가 DOM을 선택할때 더빠르게 찾겠지라는 생각으로 아래와 같이 지나치게 디테일한 selector를 작성하면 정반대로 좋지않은 성능을 보이게 됩니다.



$( ".data table.attendees td.gonzalez" );

// Better: Drop the middle if possible.
$( ".data td.gonzalez" );
결국 각 레이어를 모두 돌면서 엘리먼트를 선택하는 것이기 때문에 최대한 심플하게 selector를 작성하는 것이 selector의 성능을 향상시키는 방법입니다.

3) ID-Based Selctors

일단 검색의 시작은 ID기반으로 하는 것이 가장 좋습니다.



// Fast:
$( "#container div.robotarm" );

// Super-fast:
$( "#container" ).find( "div.robotarm" );
여기서 보면 $(...) 구문안에 여러 엘리먼트의 정보를 넣는데... 이거 좋지 않습니다.
위의 코드의 경우에는 document.querySelectorAll()을 통해서 DOM을 선택하지만 두번째의 경우에는 document.getElementById() 를 통해 DOM을 선택하기에 훨씬 빠릅니다. 물론 추가적으로 find() 메서드가 붙어 성능상이 감소가 조금있지만 그래도 첫번째 보다는 빠릅니다.




6.Use Stylesheets for Changing CSS on Many Elements


20개입니다. 20개 이상의 엘리먼트의 스타일을 jQuery를 통해서 설정해야 한다면 head 태그에 style을 정의 하는 것이 좋습니다.



// Fine for up to 20 elements, slow after that:
$( "a.swedberg" ).css( "color", "#0769ad" );

// Much faster:
$( "<style type=\"text/css\">a.swedberg { color: #0769ad }</style>")
    .appendTo( "head" );


일단 여기까지가 jQuery 가 공식적으로 가이드하는 성능관련 코딩 방법입니다. 위의 방법을 인지하고 코드를 작성해 나가면 훨씬 좋은 성능의 어플리케이션을 만들수가 있으실겁니다.

마지막으로 jQuery 소스코드는 아시다시피 오픈소스입니다. 그리고 javascipt로 매우 잘짜여진 유서깊은? 코드입니다. 한번 코드를 분석해보는 것도 javascipt를 이해하는 좋은 방법이라고 생각합니다. 그리고 내가 사용하는 jQuery 를 잘 이해하고 사용하는 방법이기도 하고요..
profile
List of Articles
번호 제목 글쓴이 날짜 조회 수
공지 [Web] 클라우드 IDE + 2 title: MoonBlonix 2017-06-25 15126
132 [vsftpd] root(루트) 접속 title: MoonBlonix 2018-04-15 1733
131 [php] 모바일 인식 title: MoonBlonix 2018-03-19 1364
130 [nginx] php 502 Bad Gateway 에러 해결 title: MoonBlonix 2018-03-19 1416
129 라즈베리파이를 위한 nginx와 php7.0 저장소 title: MoonBlonix 2018-02-24 1471
128 [javascript] Date사용 및 날자 계산 title: MoonBlonix 2017-12-29 1619
127 [php] 달력 구현 소스 + 1 title: MoonBlonix 2017-12-25 1859
126 [javascript] 팝업 열기 title: MoonBlonix 2017-12-18 1728
125 [php] 날짜/시간 함수정리 title: MoonBlonix 2017-12-09 1583
124 [mysql] 조건문 사용 (if, case), (isnull, ifnull, nvl) title: MoonBlonix 2017-12-07 1362
123 [mysql] 서브쿼리(subquery) + 1 title: MoonBlonix 2017-12-07 1891
122 [jQuery] select box 제어 title: MoonBlonix 2017-10-14 1639
121 [mysql] mysqli_fetch_array, mysqli_fetch_assoc 비교 title: MoonBlonix 2017-10-12 1479
120 [php] 조회수 중복방지 구현 + 1 title: MoonBlonix 2017-10-06 1573
119 [mysql] JOIN 사용 + 2 title: MoonBlonix 2017-10-04 1376
118 [DataBase] ORM(Object-Relational Mappings)에 대하여 title: MoonBlonix 2017-10-04 1342
117 CK에디터 파일(백업용) file title: MoonBlonix 2017-09-25 1551
116 [javascript] js, css 파일 동적 로딩 title: MoonBlonix 2017-09-20 1398
115 [web] 페이지 속도 개선 + 1 title: MoonBlonix 2017-09-17 1378
114 [jQuery] html 갈아엎기 title: MoonBlonix 2017-09-16 1619
» [jQuery] 성능 최적화 title: MoonBlonix 2017-09-15 1859