jQuery에서 selector 사용하기

예제 기준 · jQuery : v3.7.1

selector는 DOM에서 원하는 요소를 찾기 위한 표현입니다. jQuery에서는 CSS와 같은 id·class·태그·속성 선택자를 사용할 수 있고, 현재 상태나 요소 간 관계로 범위를 좁힐 수도 있습니다.

각 절의 코드는 해당 예제에서 따로 실행하며 HTML 요소가 만들어진 뒤 사용합니다. jQuery 코드는 jQuery가 로드된 화면을 기준으로 합니다. 본문 코드는 선택 대상과 개수를 확인하는 핵심 부분이며, 아래 예제에서는 버튼을 눌러 선택한 요소를 강조하고 이름과 개수를 화면에 표시합니다.

id, class, tag로 요소 선택하기

예제에는 li 세 개가 있습니다. A는 id가 target이고 item class를 가지며, B도 item class를 가집니다. C에는 item class가 없습니다.

jQuery

const target = $('#target');
const items = $('.item');
const listItems = $('li');

console.log(target.length);    // 1: A
console.log(items.length);     // 2: A, B
console.log(listItems.length); // 3: A, B, C

if (target.length > 0) {
    console.log(target[0].id); // target
}

JavaScript

const target = document.querySelector('#target');
const items = document.querySelectorAll('.item');
const listItems = document.querySelectorAll('li');

if (target !== null) {
    console.log(target.id); // target
}
console.log(items.length);     // 2
console.log(listItems.length); // 3

$("#target")와 document.querySelector("#target")는 같은 요소를 찾지만 반환 객체는 다릅니다. jQuery는 요소를 담은 jQuery 객체를, querySelector()는 첫 번째 DOM Element 또는 null을 반환합니다. querySelectorAll()은 일치하는 요소들을 담은 NodeList를 반환하며, 없으면 length가 0입니다.

jQuery 객체의 존재 여부는 .length로 확인합니다. 비어 있는 jQuery 객체도 if 조건에서는 참으로 평가되므로 if (target)만으로 요소를 찾았는지 판단할 수 없습니다. target[0]은 내부 DOM 요소이며, 반대로 DOM 요소를 $(element)로 감싸면 jQuery 메서드를 사용할 수 있습니다.

id는 문서 안에서 유일해야 합니다. class나 태그 선택자는 여러 요소에 일치할 수 있으므로 첫 요소만 필요한지 전체가 필요한지에 따라 querySelector()와 querySelectorAll()을 구분합니다. NodeList에 jQuery의 .addClass()를 호출하거나 jQuery 객체에 DOM의 classList를 바로 사용하는 것은 올바른 접근이 아닙니다.

부모와 자식 관계로 선택하기

#list 바로 아래에는 A와 C가 있고, B는 A 안의 중첩된 ul 아래에 있습니다. 공백은 깊이에 관계없는 자손(descendant), >는 바로 아래 자식(child)을 뜻합니다.

jQuery

const descendants = $('#list .item');
const children = $('#list > .item');

console.log(descendants.length); // 3: A, B, C
console.log(children.length);    // 2: A, C

JavaScript

const descendants = document.querySelectorAll('#list .item');
const children = document.querySelectorAll('#list > .item');

console.log(descendants.length); // 3: A, B, C
console.log(children.length);    // 2: A, C

A를 강조하면 그 안의 B도 테두리 영역 안에 보일 수 있지만, B 자체가 선택됐는지는 결과 목록과 개수로 확인할 수 있습니다. 중첩된 목록까지 처리하려면 공백을, 현재 목록의 직계 항목만 처리하려면 >를 사용합니다. 두 선택자 모두 #list 자체를 선택하지는 않습니다.

attribute로 선택하기

속성 선택자는 별도 class 없이도 name, data-* 또는 type으로 요소를 찾습니다. 이 예제의 A input은 name="sample", B div는 data-type="fruit"입니다. C checkbox에는 두 속성이 모두 있고 D div의 data-type은 other입니다.

jQuery

const named = $('[name="sample"]');
const fruits = $('[data-type="fruit"]');
const checkboxes = $('input[type="checkbox"]');

console.log(named.length);      // 2: A, C
console.log(fruits.length);     // 2: B, C
console.log(checkboxes.length); // 1: C

JavaScript

const named = document.querySelectorAll('[name="sample"]');
const fruits = document.querySelectorAll('[data-type="fruit"]');
const checkboxes = document.querySelectorAll('input[type="checkbox"]');

console.log(named.length);      // 2: A, C
console.log(fruits.length);     // 2: B, C
console.log(checkboxes.length); // 1: C

대괄호 안의 속성과 값이 일치하는 요소를 찾습니다. data-*는 사용자 정의 속성을 뜻하는 표기이며 여기서는 실제 속성명인 data-type을 사용합니다. type="checkbox" 조건은 입력 종류를 찾으므로 체크 여부에 관계없이 C가 선택됩니다.

아래 예제는 input이 선택되면 주변 label이나 상자도 함께 강조합니다. 화면에서 강조된 상자 수가 아니라 결과에 표시되는 실제 요소 개수를 기준으로 확인하세요.

현재 상태를 기준으로 선택하기

상태 예제는 #demo 안에서만 검색합니다. Apple checkbox는 처음에 체크돼 있고 Banana checkbox는 해제돼 있습니다. 과일 select는 Apple과 Orange option을 가지며, 비활성 input #disabledText도 있습니다.

jQuery

const demo = $('#demo');
const checked = demo.find(':checked');
const selected = demo.find(':selected');
const disabled = demo.find(':disabled');

console.log('checked:', checked.length);
console.log('selected:', selected.length);
console.log('disabled:', disabled.length);

// checkbox와 radio의 선택 상태만 필요할 때
const checkedInputs = demo.find('input:checked');
console.log('체크된 input:', checkedInputs.length);

JavaScript

const demo = document.querySelector('#demo');
const checked = demo.querySelectorAll(':checked');
const selected = demo.querySelectorAll('option:checked');
const disabled = demo.querySelectorAll(':disabled');

console.log('checked:', checked.length);
console.log('selected:', selected.length);
console.log('disabled:', disabled.length);

const checkedInputs = demo.querySelectorAll('input:checked');
console.log('체크된 input:', checkedInputs.length);

:checked는 checkbox·radio의 현재 체크 상태를 찾는 데 유용합니다. 선택된 option도 포함하므로 input만 필요하면 input:checked로 좁힙니다. 예제의 초기 HTML 상태에서는 Apple checkbox와 Apple option이 일치해 :checked는 2개, :selected와 :disabled는 각각 1개입니다.

:selected는 jQuery 확장 선택자로, querySelector()/querySelectorAll()에는 그대로 전달할 수 없습니다. JavaScript에서는 option:checked를 사용하거나 option의 selected 프로퍼티를 확인합니다. 개별 input의 체크 여부는 checked, 직접 설정된 비활성 상태는 disabled 프로퍼티로 읽을 수 있습니다.

jQuery: 개별 상태 확인

console.log($('#apple').prop('checked'));
console.log($('#optionApple').prop('selected'));
console.log($('#disabledText').prop('disabled'));

JavaScript: 개별 상태 확인

console.log(document.querySelector('#apple').checked);
console.log(document.querySelector('#optionApple').selected);
console.log(document.querySelector('#disabledText').disabled);

checked·selected HTML 속성은 기본 상태와 관련되므로 현재 선택 상태를 찾을 때 [checked]나 [selected]로 대신하지 않습니다. :disabled도 단순한 [disabled] 속성 존재 조건과는 다릅니다. 예를 들어 비활성 fieldset의 영향을 받는 입력은 자기 disabled 프로퍼티가 false여도 :disabled에 일치할 수 있습니다.

‘input 비활성화’ checkbox #disableText는 #demo 밖에 있어 검색 결과에 포함되지 않습니다. 이 설정을 해제하면 #disabledText가 활성화됩니다. 아래 예제는 상태 변경 시 다시 조회하고, 선택된 option은 별도의 미리보기 상자로도 강조합니다.

이미 얻은 jQuery 선택 집합이나 querySelectorAll()의 NodeList가 상태 변화에 맞춰 자동으로 재선별되지는 않습니다. 상태를 바꾼 뒤 같은 조회 코드를 다시 실행해야 현재 일치하는 요소를 얻습니다.

여러 조건의 요소를 한 번에 선택하기

쉼표로 연결하면 어느 하나의 선택자에 일치하는 요소를 함께 선택합니다. A는 primary, B는 secondary, C는 두 class 모두를 가지며 D는 other입니다.

jQuery

const primary = $('.primary');
const secondary = $('.secondary');
const combined = $('.primary, .secondary');

console.log(primary.length);   // 2: A, C
console.log(secondary.length); // 2: B, C
console.log(combined.length);  // 3: A, B, C

JavaScript

const primary = document.querySelectorAll('.primary');
const secondary = document.querySelectorAll('.secondary');
const combined = document.querySelectorAll('.primary, .secondary');

console.log(primary.length);   // 2: A, C
console.log(secondary.length); // 2: B, C
console.log(combined.length);  // 3: A, B, C

두 조건에 모두 일치하는 C도 결과에는 한 번만 포함됩니다. .primary, .secondary는 둘 중 하나를 만족하는 조건이고, 쉼표 없이 붙인 .primary.secondary는 같은 요소가 두 class를 모두 가져야 한다는 조건입니다.

범위를 지정할 때도 쉼표 양쪽을 확인해야 합니다. #demo .primary, .secondary는 secondary를 문서 전체에서 찾습니다. 둘 다 #demo 안으로 제한하려면 #demo .primary, #demo .secondary로 쓰거나 #demo를 먼저 찾은 뒤 내부를 검색합니다.

선택한 요소에서 다시 범위 좁히기

filter 예제의 #list에는 item 세 개가 있고 A와 C만 primary class를 가집니다. .find()는 현재 요소의 자손을 찾고, .filter()와 .not()은 이미 선택한 집합에서 요소를 남기거나 제외합니다.

jQuery

const list = $('#list');
const found = list.find('.item');

const items = $('#list .item');
const primary = items.filter('.primary');
const others = items.not('.primary');

console.log(found.length);   // 3: A, B, C
console.log(primary.length); // 2: A, C
console.log(others.length);  // 1: B
console.log(items.length);   // 원래 집합은 여전히 3개

JavaScript

const list = document.querySelector('#list');
const found = list.querySelectorAll('.item');

const items = document.querySelectorAll('#list .item');
const primary = [];
const others = [];
for (let i = 0; i < items.length; i++) {
    if (items[i].matches('.primary')) {
        primary.push(items[i]);
    } else {
        others.push(items[i]);
    }
}

console.log(found.length);   // 3: A, B, C
console.log(primary.length); // 2: A, C
console.log(others.length);  // 1: B
console.log(items.length);   // 3

JavaScript는 선택한 부모의 querySelectorAll()로 자손을 찾고, 이미 얻은 집합은 matches()로 조건을 확인하며 나눌 수 있습니다. 여기서 found와 items는 NodeList, primary와 others는 DOM 요소를 담은 배열입니다. 결과에 jQuery 메서드를 그대로 호출할 수는 없습니다.

.find()는 자기 자신을 검사하지 않습니다. item 요소가 primary class도 가진 경우 그 요소를 남기려면 .filter(".primary")를 사용합니다. .find(".primary")를 사용하면 그 요소 안쪽의 자손을 찾으므로 목적이 달라집니다. .filter()와 .not()은 원래 집합을 바꾸지 않고 새 jQuery 객체를 반환합니다.

아래 예제의 점선은 처음 선택한 집합, 실선과 [선택] 표시는 최종 결과입니다. find 버튼은 #list 한 개에서 자손 세 개로, filter/not 버튼은 item 세 개에서 각각 두 개와 한 개로 좁힙니다. 긴 선택자를 한 번에 만들기보다 탐색 범위와 필터 조건을 나누면 중간 개수도 확인하기 쉽습니다.

jQuery와 JavaScript 주요 대응 관계

목적 jQuery JavaScript
id로 찾기 $('#target') document.querySelector('#target')
class 전체 찾기 $('.item') document.querySelectorAll('.item')
선택 개수 $items.length nodeList.length
자손 / 직계 자식 #list .item / #list > .item 같은 CSS 선택자 사용
속성값으로 찾기 $('[name="sample"]') document.querySelectorAll('[name="sample"]')
선택된 option $('option:selected') document.querySelectorAll('option:checked')
내부 탐색 $list.find('.item') list.querySelectorAll('.item')
현재 집합 필터링 $items.filter('.primary') 반복문에서 matches()가 참인 요소 수집
현재 집합에서 제외 $items.not('.primary') 반복문에서 matches()가 거짓인 요소 수집

요소를 찾을 때는 선택 조건과 검색 범위, 결과 개수를 함께 확인합니다. CSS 선택자는 두 방식에서 대부분 공유할 수 있지만 jQuery 확장 선택자와 반환 객체의 차이는 구분해야 합니다. 자손을 찾는 단계와 현재 집합을 거르는 단계를 나누면 복잡한 DOM에서도 의도를 읽기 쉬운 코드를 만들 수 있습니다.

참고자료