Javascript에서 요소에 접근하는 방법은 다양합니다.
그러나 JQuery 라이브러리를 쓸 때는 간편했던 이 HTML 요소 순환이 Javascript에서는 그리 간단하지 않습니다.
모던 브라우저에서는 HTML 요소 순환을 지원하는 경우가 있으나,
범용으로 사용하려면 Hack을 사용하는 것이 제일 안전합니다.
이 글에서는 요소를 순환하는 방법 몇 가지와 Javascript에서 Element 배열을 다루는 구조에 대해서 살펴보겠습니다.
document.querySelectorAll('li').forEach((elem, index) => {
...
});
모던 브라우저 환경이라면 위와 같은 코드를 사용할 수 있습니다. JQuery의 each 메서드를 써 본 적이 있다면 별로 다르지 않네? 라는 생각이 들 겁니다.
Element 객체의 프로토타입 메서드인 querySelectorAll이 NodeList를 반환하고, NodeList 객체의 프로토타입 메서드인 forEach를 이용한 것이죠. IE는 NodeList 객체에 forEach 메서드를 지원하지 않습니다(브라우저 지원 현황은 MDN 참조).
IE11의 콘솔에서 확인해 보면 지원하지 않는다는 것을 알 수 있습니다.
Edge 최신 버전에서는 지원하는 모습입니다.
TypeScript에서도 NodeList 객체에 그냥 forEach 메서드를 쓰려고 하면 typeError를 뱉습니다.
그렇다면 아직 표준이 아닌가요? 네, 그렇습니다. 위 MDN 링크에서 살펴보면 알 수 있듯 아직 권고 사항이지 표준은 아닙니다. 위에서 말했듯 저 forEach 메서드는 Array 객체의 프로토타입 메서드가 아닌 NodeList 객체의 프로토타입 메서드입니다. 같은 기능을 수행하지만 엄연히 다른 메서드입니다.
그리고 한 가지 더, NodeList의 사촌인 HTMLCollection 객체는 아예 forEach 메서드를 지원하지 않습니다.
(크롬 개발자 콘솔에서 확인한 모습)
대표적으로 Element.prototype의 getElementsByClassName이나 children 등이 HTMLCollection 객체를 반환하는데
document.getElementsByClassName('.list').forEach((elem, index) => {
...
});
이런 코드는 실행 불가능하다는 이야기입니다.
그렇다면 어떻게 요소를 순회할 수 있을까요? 조금만 머리를 써 보면 NodeList든 HTMLCollection이든 list[index]와 같은 형태로 접근할 수 있는 '유사 배열 객체'임을 알 수 있습니다.
const list = document.querySelectorAll('li');
...
for (let index = 0; index < list.length; index++) {
console.log(list[index]);
}
또는 for of 문을 사용할 수도 있습니다. 파이어폭스가 처음으로 지원했고 지금은 대부분의 모던 브라우저가 지원합니다.
const list = document.getElementsByClassName('.list');
...
for (let item of list) {
console.log(list[index]);
}
for in 문을 사용할 수도 있으나, 객체의 다른 프로퍼티까지 순회하므로 일반 for 문이나 for of를 쓰는 게 낫습니다.
혹은 call 메서드로 Array.prototype.forEach 메서드에 바인딩 해서 사용하는 것도 가능합니다.
const list = document.getElementsByClassName('.list');
...
Array.prototype.forEach.call(list, (elem, index) => {
console.log(elem, index);
});
call의 첫 번째 인자는 this를 대체하고, 나머지 인자는 함수의 인자를 대체합니다. Array.prototype을 쓰는 게 너무 길어 보인다면 [].forEach.call이나 new Array().forEach.call 이런 식으로 call을 사용해도 좋습니다. 본인은 위와 같이 쓰는 편이 명확해서 조금 길어보여도 저렇게 표현합니다.
이 외에도 직접 함수를 만들거나
NodeList.prototype.forEach = callback => {
for (let index = 0; index < this.length; index++) {
callback(this[index], index);
}
}const list = document.getElementsByClassName('.list');
...
list.forEach((elem, index) => {
console.log(elem, index);
});
배열로 복사해서 사용하는 방법도 있습니다.
const list = document.getElementsByClassName('.list');
...
[...list].forEach((elem, index) => {
console.log(elem, index);
});
다만 이 방법은 최신 문법을 지원하지 않는 브라우저에서는 트랜스파일러를 쓰거나, 직접 for 문을 돌려 배열을 복사해야 합니다.
NodeList.prototype.forEach = Array.prototype.forEach;
위처럼 Array.prototype의 forEach 메서드와 NodeList.prototype의 forEach를 연결해 쓸 수도 있습니다. 하지만 예상치 않은 결과가 나올 수 있으니 주의해야 합니다. 코딩 방법으로도 그다지 좋은 예는 아닙니다.
Array.from(document.querySelectorAll(.list)).forEach((elem, index) => {
...
})