리스트로 된 html 요소를 추출하고자 할때, 특히 웹크롤을 하고자할때 분명 배열로 추출된 것 같은데 배열 메서드를 쓰려고만 하면 에러가 발생할 때가 있습니다. 그 에러를 겪다가 삽질 후 다음번에는 시행착오를 겪지 않기 위해 이 글을 작성합니다.

아래와 같은 html이 있다고 가정하겠습니다.

<ul id="list">
  <li class="item">1</li>
  <li class="item">2</li>
  <li class="item">3</li>
  <li class="item">4</li>
  <li class="item">5</li>
  <li class="item">6</li>
  <li class="item">7</li>
  <li class="item">8</li>
  <li class="item">9</li>
  <li class="item">10</li>
</ul>




document 객체나 jquery 둘 중 아무거나 써도 상관 없습니다. 어차피 같은 javascript잖아요? 성능은 좋지 않아도 빠르게 데이터를 뽑아내야 할 경우엔 jquery를 사용하는 것을 더 선호한답니다. 두 가지 경우 모두 예시를 보도록 하겠습니다.

DOM을 사용하는 방법

유사배열인 NodeList가 콘솔에 찍히는 것을 확인할 수 있습니다.

document.querySelectorAll('#list > .item') 

// NodeList(10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]

jquery를 사용하는 방법

유사배열인 HTMLCollection이 콘솔이 찍히는 것을 확인할 수 있습니다.

$('#list').children

// HTMLCollection(10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]




둘 다 배열이 아니라서 배열 메서드를 사용하려고 하면 에러가 발생하거나 자식 요소를 볼 수 없게 됩니다.

DOM을 사용하는 경우

document.querySelectorAll('#list > .item').map(el => el)

Uncaught TypeError: document.querySelectorAll(…).map is not a function at :1:46

jquery를 사용하는 경우

$('#list > .item').children().map(el => el)

C.fn.init [prevObject: C.fn.init(0)]




때문에 유사배열인 HTMLCollectionNodeList를 진짜 배열로 바꿔줘야 다음 작업이 진행가능합니다. 물론 jquery에서는 다음과 같은 경우라면 toArray() 메서드만 사용하면 바로 배열로 변경도 가능합니다. 아래와 같은 경우들을 제외하고 말하는거에요.

toArray()를 사용하는 경우

$('#list > .item')

// C.fn.init(10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, prevObject: C.fn.init(1)]

$('#list > .item').toArray()

// (10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]

double dollar sign을 사용하는 경우

$$('.item')

// (10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]




이제부터 배열 메서드나 트릭을 사용해서 간단하게 유사배열을 배열로 바꿔볼겁니다.

Array.from()를 사용하는 방법

유사 배열 객체나 반복 가능한 객체를 얕게 복사해 새로운 Array객체를 만드는 방법입니다.

Array.from(document.querySelectorAll('#list > .item'))

// (10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]

Array.slice()를 사용하는 방법

참고로 인자로는 start, end값이 들어가는데, end는 생략 가능합니다.

Array.slice(document.querySelectorAll('#list > .item'))

// (10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]

스프레드 연산자를 사용하는 방법

[...document.querySelectorAll('#list > .item')]

// (10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]




ES6 이전이었다면 다음과 같은 구문을 사용했을테지만 매번 배열로 변경할때마다 아래와 같은 방법을 사용하지 않아도 된다는 것은 정말 ECMA재단에 정말 감사할 일입니다. 특히 마지막에 함수를 한 번 돌리는 구문은 눈물납니다 ㅠㅠ

Array.prototype.slice.call(document.querySelectorAll('#list > .item'))
// (10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]

[].slice.call(document.querySelectorAll('#list > .item'))
// (10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]

function toArray(x) {
    for(var i = 0, a = []; i < x.length; i++)
        a.push(x[i]);
    return a
}
toArray(document.querySelectorAll('#list > .item'))
// (10) [li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item, li.item]

다른 궁금하신 점이나 수정해야할 부분이 있다면 댓글 달아주시면 감사하겠습니다 :)