jQuery


jQuery는 파편화된 브라우저 환경에서 크로스 브라우징 이슈를 쉽게 처리할 수 있도록 관련 API를 제공하는 자바스크립트 라이브러리이다. 2006년 존 레식에 의해 처음 공개된 이후 10년이 넘도록 자바스크립트에서 가장 인기있는 라이브러리로써 널리 사용되었다. 최근에는 표준 API만으로도 DOM이나 Ajax 요청 등을 편리하게 다룰 수 있게 되었고 브라우저 간의 차이가 크게 줄어들면서 사용이 감소하는 추세이다. 또한 SPA(Single-page Application)가 대중화되면서 리액트, 뷰 등의 프레임워크가 jQuery의 역할을 상당 부분 대신하고 있다. 하지만 SPA로 작성할 필요가 없거나 구형 브라우저를 지원해야 하는 경우 jQuery는 여전히 유용하며, 크로스 브라우징 문제를 고민하지 않고 간결한 코드를 작성할 수 있도록 해 준다.

이 가이드는 jQuery를 사용할 때 더 효율적이고 유지보수 하기 좋은 코드를 작성하도록 돕기 위해 작성되었다. jQuery API에 대한 상세한 설명은 공식 홈페이지를 참고하기 바란다.

목차

버전 선택하기

1.X, 2.X, 3.X 버전 모두 파이어폭스, 크롬, 사파리, 오페라 등 다양한 브라우저를 지원한다. jQuery 버전을 선택하여 서비스의 브라우저 지원 범위를 만족하게 할 수 있다.

  • 1.X 버전 : IE6 이상 버전을 지원
  • 2.X, 3.X 버전 : IE 6~8버전은 지원하지 않으며, IE9 이상 버전을 지원

    1.7.0 이하 버전은 너무 오래된 버전이라 최신 플러그인 및 최신 브라우저와 충돌이 있으므로 사용하지 않는다. 최신 버전은 이전 버전에 비해 더 안정적이기 때문에 되도록 최신 버전을 사용하는 것이 좋다.

  • 2018년 9월 기준 3.X 버전이 최신이며, IE 9 이상을 지원하고 있다.

설치하기

npm, bower, CDN으로 설치하거나 저장소에서 직접 내려받을 수 있으며, 프로젝트 성격에 따라 적절히 사용한다.

npm 사용하기

npm으로 소스코드를 가져올 경우 아래와 같은 커맨드 라인 명령을 사용한다.

$ npm install --save jquery # 최신 버전
$ npm install --save jquery@<version> # 특정 버전

bower 사용하기

bower로 소스코드를 가져올 경우 아래와 같이 커맨드 라인 명령을 사용한다.

$ bower install jquery # 최신 버전
$ bower install jquery#<tag> # 특정 버전

CDN 사용하기

다음 예시처럼 https://code.jquery.com/에서 원하는 버전을 찾아 CDN을 이용해 바로 사용할 수 있다. jQuery에서 제공하는 CDN뿐만 아니라 Google, Microsoft에서 제공하는 CDN을 사용할 수도 있다.

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

CDN을 사용할 경우 외부 사정에 의해 URL이 변경되거나 장애가 발생했을 때 서비스가 영향을 받을 수 있으므로 사용을 지양한다.

참고 [FE 가이드] 안티 패턴 - jQuery 같은 외부 소스는 다운로드해서 사용하라

직접 다운로드하기

필요한 버전의 jQuery를 공식 사이트에서 다운로드하여 사용한다.

$ 식별자

jQuery는 jQuery라는 하나의 함수로 구성되어 있으며, 이 함수가 네임스페이스의 역할과 더불어 jQuery의 모든 기능을 제공한다. jQuery를 전역으로 로드하면 $ 식별자를 jQuery 함수의 별칭으로 지정해 주기 때문에 편의상 $ 식별자를 사용한다. 하지만 prototype.js과 같이 $ 식별자를 쓰는 자바스크립트 라이브러리를 이미 사용하는 경우 jQuery의 $ 식별자가 기존 식별자를 덮어쓸 수 있다. 이 경우 다음과 같이 $.noConflict() 메서드를 호출하면 기존의 $ 식별자가 갖고 있던 값을 복원시켜주므로 기존 라이브러리와 병행해서 사용할 수 있다.

<script src="other_lib.js"></script>
<script src="jquery.js"></script>
<script>
$.noConflict();
jQuery( document ).ready(function( $ ) {
  // jQuery의 $만 사용할 수 있다.
});
// 다른 라이브러리의 $를 사용할 수 있다.
</script>

DOM 탐색

jQuery 객체가 할당된 변수는 변수명 앞에 $를 붙인다

jQuery 객체가 할당된 변수에는 $를 붙여 네이티브 DOM 엘리먼트와 jQuery 객체를 쉽게 구분할 수 있다.

// good
const $myId = $('#myId');

// Bad
const myId = $('#myId');

가능하면 아이디 선택자를 사용한다

아이디 선택자를 사용하면 jQuery가 내부에서 document.getElementById()를 사용하여 엘리먼트를 찾기 때문에 성능상 이점이 있다.

// Good
document.getElementById("myId");
$("#myId");

// Bad
$('.myClass');

아이디 선택자는 다른 선택자와 같이 사용하지 않는다

아이디는 문서에 고유한 값이기 때문에 다른 조건과 같이 사용하는 것은 의미가 없다.

// Good
$('#inner');

// Bad
$('#outer #inner');

클래스 선택자는 태그 선택자와 결합하여 사용하지 않는다

// Good
const $products = $('.products');

// Bad
const $products = $('div.products');

선택자를 적절한 순서로 작성한다

좌측엔 "태그명" 또는 "클래스명"을, 마지막엔 "태그명.클래스명"이 오도록 작성하면 더욱 빠르게 찾을 수 있다. 예를 들어 "li.item"이 온다면 태그이름이 "li"인 모든 엘리먼트를 찾고, 그중에 "item" 클래스명을 가진 모든 엘리먼트를 찾는다. 그 다음에야 뒤에 오는 선택자를 이용해서 다시 DOM 노드를 탐색해 나가기 때문이다.

// Good
$('.wrap li.item');

// Bad
$('ul.wrap .item');

복잡한 선택자는 피한다

선택자 길이를 최대한 짧게 유지하고 2개가 넘지 않도록 주의한다. 복잡한 선택자는 성능을 느리게 만들며 가독성을 나쁘게 한다.

<div id="container" class="container-class first">
  <table id="table-id">
    <tr>
      <th class="first">Firstname</th>
      <th>Lastname</th> 
      <th>Age</th>
    </tr>
    <tr>
      <td  class="first">Jill</td>
      <td>Smith</td> 
      <td>50</td>
    </tr>
    <tr>
      <td class="first">Eve</td>
      <td>Jackson</td> 
      <td>94</td>
    </tr>
  </table>
</div>

아래는 위 HTML코드에서 테이블에서 first 클래스를 가진 모든 요소를 선택하는 예시이다.

// Good
$('#table-id .first');

// Bad
$('#container table#table-id tr th.first, td.first')

탐색 범위를 좁힐 수 있도록 선택자에 context 정보를 제공한다

// Good: first-item 라는 아이디를 가진 요소를 대상으로 확인하기 때문에 더 빠른 결과를 얻을 수 있다.
$('.item','#first-item'); 

// Bad: item 클래스를 가진 대상을 전부 확인하기 때문에 느린 결과를 얻을 것이다.
$('.item'); 

특정 아이디를 기준으로 자식 엘리먼트 목록을 탐색할 때는 .find()를 사용한다

Sizzle 선택자 엔진을 거치지 않기 때문에 빠르다.

// Good - #products는 내부적으로 이미 document.getElementById()를 통해 찾은 뒤 div.container 만 Sizzle 선택자 엔진을 거치게 되므로 빠르다.
const $productIds = $('#products').find('div.container');

// Bad - 중첩된 query 모두 Sizzle 선택자 엔진을 통한다.
const $productIds = $('#products div.container');

전역 선택자는 피한다

// Good
$('.buttons').children();
$('.category input:radio'); 

// Bad
$('.buttons > *');
$('.category *:radio'); 

가상 선택자를 사용할 때 태그 선택자를 생략하지 않는다

가상 선택자를 태그 선택자 없이 사용하면 모든 엘리먼트를 검색하게 되어 성능이 느려진다.

// Good
$('div.someclass input:radio');

// Bad
$('div.someclass :radio');

DOM 조작

이미 존재하는 엘리먼트를 조작하기 전에는 항상 detach()한다

detach()를 사용하면 해당 엘리먼트를 실제 DOM에서 분리하여 메모리상에서만 조작할 수 있어, 불필요한 레이아웃 연산이 여러 번 발생하는 것을 방지할 수 있다.

<div id="table_container">
  <table>
    <tr>
      <td>ID</td>
      <td>Name</td>
    </tr>
  </table>
</div>

// Good
<script type="text/javascript">
  const parent = $( "#table_container" );
  const table = parent.find('table');
  table.detach();
  table.append('<tr><td>1</td><td>John Smith</td></tr>');
  parent.append(table);
</script>

// Bad
<script type="text/javascript">
  $('#table_container table').append('<tr><td>1</td><td>Hanjung</td></tr>');
</script>

엘리먼트가 있는지 확인하고 실행한다

// Good
const $mySelection = $('#nosuchthing');
if ($mySelection.length) {
    $mySelection.slideUp();
    ...
}

// Bad: 선택자에 아무것도 없다는 것을 여러 과정이 진행 된 이후 알아 차릴 것이다.
$('#nosuchthing').slideUp();

인라인 CSS 스타일은 되도록 사용하지 않는다

CSS와 자바스크립트는 서로 다른 책임을 갖기 때문에, 각각을 분리해서 관리하는 것이 유지보수 측면에서 유리하다. 되도록이면 CSS에서 클래스를 정의하고, 자바스크립트에서 클래스를 추가, 삭제하여 스타일을 조작한다.

/* Good */
.error { color: red; font-weight: bold; }
// Good
$("#mydiv").addClass("error");

// Bad
$("#mydiv").css({'color':red, 'font-weight':'bold'});

이벤트 처리

HTML 마크업에 직접 이벤트 핸들러를 설정하지 않는다

HTML 마크업에 직접 이벤트 핸들러를 설정하면 이벤트를 동적으로 설정하고 제거하는 것이 어려워진다. 또한, 디버깅 할 때 HTML 마크업과 자바스크립트를 모두 확인해야 하는 번거로움이 생긴다.

// Good
$('#myLink').on('click', myEventHandler); 
<!-- Bad -->
<a id="myLink" href="#" onclick="myEventHandler();">my link</a>

이벤트 위임을 사용한다

이벤트 위임(Event delegation)을 사용하면 동적으로 추가 또는 수정되는 자식 엘리먼트의 이벤트도 함께 처리할 수 있는 이점이 있다.

$("#parent-container").on("click", "a", delegatedClickHandler);

이벤트 핸들러에 익명 함수를 사용하지 않는다

익명 함수를 사용하면 디버깅과 관리, 그리고 테스트가 어렵다.

// Good
function myLinkClickHandler(){...}
function myInitHandler(){...}
$('#myLink').on('click', myLinkClickHandler);

$(document).ready(myInitHandler);
$(myInitHandler);
// $(document).ready(myInitHandler)과 동일, 다른 형태로 작성하는 ready 구문은 3.0에서 디프리케이트 되었기 때문에 이렇게 작성할 것을 권장

// Bad
$('#myLink').on('click', function(){...}); 
$(function(){ ... });

한 페이지에 하나의 ready 이벤트 핸들러만 사용한다

디버깅이 용이하며 동작의 흐름도 추적할 수 있다.

사용자 정의 이벤트는 .off()가 용이하도록 네임스페이스를 사용한다

(3.X 버전부터 unbind 메서드가 디프리케이트 되었으므로 대신 off() 메서드 사용을 권장한다.)

const validate = function() {
  // 검증 코드
};
 
$( "form" ).on( "click.validator", "button", validate );
$( "form" ).on( "keypress.validator", "input[type='text']", validate );

// 'validator' namespace 하위의 이벤트 핸들러를 모두 해제 시킨다.
$( "form" ).off( ".validator" );

메서드 체이닝

메서드 체이닝을 사용한다

메서드 체이닝은 변수를 캐싱하거나 선택자를 여러 번 호출하지 않아도 되는 이점이 있다.

// Good
$('#myDiv').addClass('error').show();

// Bad
$('#myDiv').addClass('error');
$('#myDiv').show();

메서드를 3번 이상 체이닝하면 가독성에 문제가 있으니 줄바꿈을 사용한다

// Good
$('#myLink')
  .addClass('bold')
  .on('click', myClickHandler)
  .on('mouseover', myMouseOverHandler)
  .show();
    
// Bad
$('#myLink').addClass('bold').on('click', myClickHandler).on('mouseover', myMouseOverHandler).show();

속성을 연속해서 변경할 때 메서드 체이닝 대신 객체 리터럴을 사용한다

// Good
$myLink.attr({
  href: '#',
  title: 'my link',
  rel: 'external'
});

// Bad
$myLink.attr('href', '#').attr('title', 'my link').attr('rel', 'external');

Ajax

요청 URL에는 프로토콜을 명시하지 않는다

프로토콜을 명시하지 않는 Protocol-relative URL을 사용하여, 브라우저가 현재 페이지의 프로토콜에 따라 자동으로 판단하도록 한다.

// Good
$.ajax({
  url: '//www.nhn.com/api/hello',
  ...
});

// Bad
$.ajax({
  url: 'https://www.nhn.com/api/hello',
  ...
});

GET 요청시 파라미터는 URL에 붙여서 사용하지 말고 data객체를 사용한다

가독성이 좋아지고 디버깅이 쉬워진다.

// Good
$.ajax({
  url: 'something.php',
  data: { 
    param1: test1, 
    param2: test2 
  }
});

// Bad
$.ajax({
  url: 'something.php?param1=test1&param2=test2',
  ....
});

필요한 데이터 타입을 명확히 알 수 있도록 dataType을 명시한다

const jqxhr = $.ajax({
  url: url,
  ...
  dataType: 'json',
  ...
});

Promise 인터페이스를 사용하면 코드의 흐름을 좀 더 직관적으로 이해할 수 있다

$.ajax({ ... }).then(successHandler, failureHandler);

// 위 코드를 다음과 같이 작성할 수 있다.
const jqxhr = $.ajax({ ... });
jqxhr.done(successHandler);
jqxhr.fail(failureHandler);

jQuery 3.0 버전과 ES2015

jQuery 3.0 버전 이후 ES2015와 호환 가능해진 내용을 예시와 함께 간략하게 설명한다 .

for...of 문에서 jQuery 컬렉션 사용할 수 있다

ES2015에서 추가된 문법인 for...of 문은 Iterable 프로토콜을 지원하는 모든 객체를 지원한다. jQuery 3.0부터 jQuery 컬렉션도 Iterable 프로토콜을 지원하여 for...of 문에서 사용할 수 있게 되었다.

const $elems = $(".someclass");
 
// 기존 jQuery 방식
$elems.each((i, elem) => {
  // elem 혹은 "this" 을 사용
});
 
// ES6 방식
for (let elem of $elems) {
  // elem을 사용
}

jQuery.Deferred는 Promises/A+와 호환된다

jQuery.ajax()의 결과로 반환되는 jqXHR 객체는 jQuery의 Deferred 객체였다. jQuery 3.0부터 Deferred객체에서 제공되던 success, error, complete 메소드는 제거되었다. 대신 Deferred 객체의 done, fail, always를 사용하게 되고, Promises/A+ 메서드인 then, catch를 사용할 수 있게 되었다.

const defer = $.Deferred();
const filtered = defer.then(null, (value) => {
  return value * 3;
});
 
defer.resolve(6);

filtered.done(( value ) => {
  alert( "Value is 3*6 =" + value );
});

jQuery.when의 인자로 then 메소드가 있는 모든 객체를 사용할 수 있다

이는 네이티브 프라미스 객체 뿐만 아니라, Bluebird 와 같은 외부 프라미스 라이브러리도 지원한다는 의미이다. 또한 인자가 여러 개 있는 경우 Promise.all()처럼 동작하며, 매개변수가 없거나 하나일 때는 Promise.resolve()처럼 동작한다.

const promise1 = new Promise((resolve, reject) => {
  resolve('foo');
});

$.when(promise1).then((value) => { 
  console.log(value); // "foo"
});

jQuery.ready를 프라미스 형태로 사용할 수 있다

jQuery.when이나 Promise.resolve()를 이용하면 jQuery.ready를 프라미스 형태로 변환하여 사용할 수 있다.

$.when($.ready, $.getScript("optional.js")).then(() => {
 // document가 ready 되고 optional.js가 loaded/run 되었을 때 
}).catch(() => {
  // error가 발생할 경우
});

맺음말

지금까지 jQuery를 사용할 때 유의할 점과 권장 사항, 3.0 버전에 추가된 ES2015 지원 등을 살펴 보았다. jQuery는 유용한 도구이지만 잘못 사용하면 성능을 오히려 나쁘게 만들거나 코드의 가독성을 떨어뜨린다. 이 가이드를 지침삼아 jQuery가 주는 이점을 최대한 활용할 수 있길 바란다.


이 문서는 NHN Cloud의 FE개발랩에서 작성하고 관리하는 공식 웹 프론트 개발 가이드이다. 가이드 적용 관련 문의나 문서의 오류, 개선 제안은 공식 문의 채널(dl_javascript@nhn.com)을 통해 할 수 있다.


Last Modified
2019. 03. 29
FE Development LabBack to list