Dethe Elza( [email protected] ), Blast Radius 수석 기술 설계자
DOM(문서 개체 모델)은 XML 및 HTML 데이터를 조작하는 데 가장 일반적으로 사용되는 도구 중 하나이지만 그 잠재력이 완전히 활용되는 경우는 거의 없습니다. DOM을 활용하고 사용하기 쉽게 만들면 동적 웹 애플리케이션을 포함한 XML 애플리케이션을 위한 강력한 도구를 얻을 수 있습니다.
이번 호에는 객원 칼럼니스트이자 친구이자 동료인 Dethe Elza가 출연합니다. Dethe는 XML을 사용한 웹 애플리케이션 개발에 대한 폭넓은 경험을 갖고 있으며 DOM 및 ECMAScript를 사용하여 XML 프로그래밍을 소개하는 데 도움을 준 그에게 감사를 표하고 싶습니다. Dethe의 더 많은 칼럼을 보려면 이 칼럼을 계속 지켜봐 주시기 바랍니다.
- David Mertz
DOM은 XML 및 HTML 처리를 위한 표준 API 중 하나입니다. 메모리 집약적이고 느리며 장황하다는 비판을 자주 받습니다. 그럼에도 불구하고 이는 많은 응용 프로그램에 대한 최선의 선택이며 XML의 다른 주요 API인 SAX보다 확실히 간단합니다. DOM은 웹 브라우저, SVG 브라우저, OpenOffice 등과 같은 도구에 점차적으로 나타나고 있습니다.
DOM은 표준이고 다른 표준에 널리 구현되고 내장되어 있기 때문에 훌륭합니다. 표준적으로 데이터 처리는 프로그래밍 언어에 구애받지 않습니다(강점일 수도 있고 아닐 수도 있지만 적어도 데이터 처리 방식을 일관되게 만듭니다). DOM은 이제 웹 브라우저에 내장되어 있을 뿐만 아니라 많은 XML 기반 사양의 일부이기도 합니다. 이제 그것은 당신의 무기고의 일부이고 아마도 아직도 가끔씩 그것을 사용하고 있을 것이므로, 그것이 테이블에 가져오는 것을 최대한 활용해야 할 때인 것 같습니다.
DOM을 사용하여 한동안 작업한 후에 패턴이 발전하는 것을 볼 수 있습니다. 즉, 반복해서 수행하고 싶은 작업입니다. 바로가기는 긴 DOM으로 작업하고 설명이 필요 없으며 우아한 코드를 만드는 데 도움이 됩니다. 다음은 몇 가지 JavaScript 예제와 함께 제가 자주 사용하는 팁과 요령 모음입니다.
insertAfter 및 prependChild의
첫 번째 트릭은
"트릭 없음"이 있다는 것입니다.DOM에는 컨테이너 노드(일반적으로 Element, Document 또는 Document Fragment)에 하위 노드를 추가하는 두 가지 메소드(appendChild(node) 및 insertBefore(node, referenceNode))가 있습니다. 뭔가 빠진 것 같습니다. 참조 노드 뒤에 하위 노드를 삽입하거나 앞에 추가하려면 어떻게 해야 합니까(새 노드를 목록의 첫 번째 노드로 만들기)? 수년 동안 내 해결책은 다음 함수를 작성하는 것이었습니다.
목록 1. 삽입 및 추가에 대한 잘못된 방법
함수 insertAfter(부모, 노드, 참조노드) {
if(referenceNode.nextSibling) {
parent.insertBefore(node, referenceNode.nextSibling);
} 또 다른 {
parent.appendChild(노드);
}
}
함수 prependChild(부모, 노드) {
if (parent.firstChild) {
parent.insertBefore(node, parent.firstChild);
} 또 다른 {
parent.appendChild(노드);
}
}
실제로 Listing 1과 마찬가지로 insertBefore() 함수는 참조 노드가 비어 있을 때appendChild()로 반환되도록 정의되었습니다. 따라서 위의 방법을 사용하는 대신 목록 2의 방법을 사용하거나 이를 건너뛰고 내장 함수만 사용할 수 있습니다.
목록 2. 이전부터 삽입하고 추가하는 올바른 방법
함수 insertAfter(부모, 노드, 참조노드) {
parent.insertBefore(node, referenceNode.nextSibling);
}
함수 prependChild(부모, 노드) {
parent.insertBefore(node, parent.firstChild);
}
DOM 프로그래밍이 처음이라면 노드를 가리키는 포인터가 여러 개 있을 수 있지만 해당 노드는 DOM 트리의 한 위치에만 존재할 수 있다는 점을 지적하는 것이 중요합니다. 따라서 트리에 삽입하려는 경우 자동으로 제거되므로 먼저 트리에서 제거할 필요가 없습니다. 이 메커니즘은 노드를 새 위치에 삽입하기만 하면 노드 순서를 변경할 때 편리합니다.
이 메커니즘에 따르면 두 개의 인접한 노드(node1 및 node2라고 함)의 위치를 바꾸려면 다음 솔루션 중 하나를 사용할 수 있습니다.
node1.parentNode.insertBefore(node2, node1);
또는
node1.parentNode.insertBefore(node1.nextSibling, node1);
DOM으로 또 무엇을 할 수 있나요?
DOM은 웹페이지에서 널리 사용됩니다. 북마크릿 사이트(참고자료 참조)를 방문하면 페이지 재정렬, 링크 추출, 이미지 숨기기 또는 플래시 광고 등을 수행할 수 있는 창의적이고 짧은 스크립트를 많이 찾을 수 있습니다.
그러나 Internet Explorer는 노드 유형을 식별하는 데 사용할 수 있는 노드 인터페이스 상수를 정의하지 않으므로 인터페이스 상수를 생략하는 경우 먼저 웹용 DOM 스크립트에서 인터페이스 상수를 정의해야 합니다.
목록 3. 노드가 정의되었는지 확인하기
if (!window['노드']) {
window.Node = 새 객체();
Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3;
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
Node.NOTATION_NODE = 12;
}
목록 4는 노드에 포함된 모든 텍스트 노드를 추출하는 방법을 보여줍니다.
목록 4. 내부 텍스트
함수 innerText(노드) {
// 이것은 텍스트 노드입니까, CDATA 노드입니까?
if (node.nodeType == 3 || node.nodeType == 4) {
node.data를 반환합니다.
}
var i;
var returnValue = [];
for (i = 0; i < node.childNodes.length; i++) {
returnValue.push(innerText(node.childNodes[i]));
}
return returnValue.join('');
}
단축키
사람들은 DOM이 너무 장황하고 간단한 기능에는 많은 코드가 필요하다고 불평하는 경우가 많습니다. 예를 들어, 텍스트를 포함하고 버튼 클릭에 응답하는
목록 5.
이러한 방식으로 노드를 자주 생성하는 경우 이 코드를 모두 입력하면 금방 지치게 됩니다. 더 나은 해결책이 있어야 했고, 실제로 있었습니다! 다음은 요소를 생성하고, 요소 속성과 스타일을 설정하고, 텍스트 하위 노드를 추가하는 데 도움이 되는 유틸리티입니다. name 매개변수를 제외한 다른 모든 매개변수는 선택사항입니다.
목록 6. elem() 함수 단축키
function elem(이름, 속성, 스타일, 텍스트) {
var e = document.createElement(이름);
if (속성) {
for (속성 키) {
if (키 == '클래스') {
e.className = attrs[키];
} else if (키 == 'id') {
e.id = attrs[키];
} 또 다른 {
e.setAttribute(key, attrs[키]);
}
}
}
if (스타일) {
for (키 스타일) {
e.style[키] = 스타일[키];
}
}
if (텍스트) {
e.appendChild(document.createTextNode(text));
}
e를 반환;
}
이 단축키를 사용하면 목록 5의
목록 7.
이 유틸리티는 다수의 복잡한 DHTML 객체를 신속하게 생성하려는 경우 많은 시간을 절약할 수 있습니다. 여기서 패턴은 자주 생성해야 하는 특정 DOM 구조가 있는 경우 유틸리티를 사용하여 생성한다는 의미입니다. 이렇게 하면 작성하는 코드의 양이 줄어들 뿐만 아니라, 반복적으로 잘라내고 붙여넣는 코드(오류의 원인)도 줄어들고, 코드를 읽을 때 명확하게 생각하기가 쉬워집니다.
다음은 무엇입니까?
DOM은 문서의 순서에 따라 다음 노드가 무엇인지 알려주는 데 어려움을 겪는 경우가 많습니다. 다음은 노드 간 앞뒤로 이동하는 데 도움이 되는 몇 가지 유틸리티입니다.
목록 8. nextNode 및 prevNode
// 문서 순서대로 다음 노드를 반환합니다.
함수 nextNode(노드) {
if (!node)는 null을 반환합니다.
if (node.firstChild){
node.firstChild를 반환합니다.
} 또 다른 {
return nextWide(노드);
}
}
// nextNode()에 대한 도우미 함수
함수 nextWide(노드) {
if (!node)는 null을 반환합니다.
if (node.nextSibling) {
node.nextSibling을 반환합니다.
} 또 다른 {
return nextWide(node.parentNode);
}
}
// 문서 순서대로 이전 노드를 반환합니다.
함수 prevNode(노드) {
if (!node)는 null을 반환합니다.
if (node.previousSibling) {
return PreviousDeep(node.previousSibling);
}
node.parentNode를 반환합니다.
}
// prevNode()에 대한 도우미 함수
함수 이전Deep(노드) {
if (!node)는 null을 반환합니다.
동안(node.childNodes.length) {
노드 = node.lastChild;
}
반환 노드;
}
DOM을 쉽게 사용하세요
때로는 각 노드에서 함수를 호출하거나 각 노드에서 값을 반환하여 DOM을 반복하고 싶을 수도 있습니다. 실제로 이러한 아이디어는 매우 일반적이기 때문에 DOM 레벨 2에는 DOM Traversal and Range(DOM의 모든 노드를 반복하기 위한 객체와 API를 정의)라는 확장이 이미 포함되어 있습니다. 이는 DOM에서 함수를 적용하고 범위를 선택하는 데 사용됩니다. . 이러한 함수는 Internet Explorer에 정의되어 있지 않기 때문에(적어도 아직은) nextNode()를 사용하여 비슷한 작업을 수행할 수 있습니다.
여기서 아이디어는 몇 가지 간단하고 일반적인 도구를 만든 다음 원하는 효과를 얻기 위해 다양한 방법으로 조합하는 것입니다. 함수형 프로그래밍에 익숙하다면 이 내용도 익숙할 것입니다. Beyond JS 라이브러리(참고자료 참조)는 이 아이디어를 발전시킵니다.
목록 9. 기능적 DOM 유틸리티
// startNode에서 시작하여 모든 노드의 배열을 반환하고
// DOM 트리의 나머지 부분을 계속 진행합니다.
함수 listNodes(startNode) {
var list = 새로운 배열();
var 노드 = startNode;
동안(노드) {
목록.푸시(노드);
node = nextNode(노드);
}
반환 목록;
}
// listNodes()와 동일하지만 startNode에서 거꾸로 작동합니다.
// 이는 listNodes()를 실행하는 것과 같지 않으며
// 목록을 뒤집습니다.
함수 listNodesReversed(startNode) {
var list = 새로운 배열();
var 노드 = startNode;
동안(노드) {
목록.푸시(노드);
node = prevNode(노드);
}
반환 목록;
}
// nodeList의 각 노드에 func를 적용하고 새로운 결과 목록을 반환합니다.
함수 맵(목록, func) {
var result_list = 새로운 배열();
for (var i = 0; i < list.length; i++) {
result_list.push(func(list[i]));
}
결과_목록을 반환합니다.
}
// 각 노드에 테스트를 적용하고, 해당 노드의 새 목록을 반환합니다.
// 테스트(노드)는 true를 반환합니다.
함수 필터(목록, 테스트) {
var result_list = 새로운 배열();
for (var i = 0; i < list.length; i++) {
if (test(list[i])) result_list.push(list[i]);
}
결과_목록을 반환합니다.
}
목록 9에는 네 가지 기본 도구가 포함되어 있습니다. listNodes() 및 listNodesReversed() 함수는 Array의 Slice() 메서드와 유사하게 선택적 길이로 확장될 수 있습니다. 주목해야 할 또 다른 점은 map() 및 filter() 함수는 완전히 일반적이며 모든 목록(노드 목록뿐만 아니라)에 대해 작업하는 데 사용할 수 있다는 것입니다. 이제 이 두 가지를 결합할 수 있는 몇 가지 방법을 보여 드리겠습니다.
목록 10. 기능 유틸리티 사용
// 문서 순서대로 모든 요소 이름 목록
함수 isElement(노드) {
return node.nodeType == Node.ELEMENT_NODE;
}
함수 nodeName(노드) {
node.nodeName을 반환합니다.
}
var elementNames = map(filter(listNodes(document),isElement), nodeName);
// 문서의 모든 텍스트(CDATA 무시)
함수 isText(노드) {
return node.nodeType == Node.TEXT_NODE;
}
함수 nodeValue(노드) {
node.nodeValue를 반환합니다.
}
var allText = map(filter(listNodes(document), isText), nodeValue);
이러한 유틸리티를 사용하여 ID 추출, 스타일 수정, 특정 노드 찾기 및 제거 등을 수행할 수 있습니다. DOM Traversal 및 Range API가 광범위하게 구현되면 먼저 목록을 작성하지 않고도 이를 사용하여 DOM 트리를 수정할 수 있습니다. 강력할 뿐만 아니라 위에서 강조한 것과 유사한 방식으로 작동합니다.
DOM의 위험 지대
핵심 DOM API를 사용하면 XML 데이터를 DOM으로 구문 분석하거나 DOM을 XML로 직렬화할 수 없습니다. 이러한 함수는 DOM 레벨 3 확장 "Load and Save"에 정의되어 있지만 아직 완전히 구현되지 않았으므로 지금은 생각하지 마세요. 각 플랫폼(브라우저 또는 기타 전문 DOM 애플리케이션)에는 DOM과 XML 간 변환을 위한 고유한 방법이 있지만 플랫폼 간 변환은 이 기사의 범위를 벗어납니다.
DOM은 매우 안전한 도구가 아닙니다. 특히 XML로 직렬화할 수 없는 트리를 생성하기 위해 DOM API를 사용할 때 더욱 그렇습니다. 동일한 프로그램에서 DOM1 비네임스페이스 API와 DOM2 네임스페이스 인식 API(예: createElement 및 createElementNS)를 혼합하지 마십시오. 네임스페이스를 사용하는 경우 루트 요소 위치에서 모든 네임스페이스를 선언하고 네임스페이스 접두사를 재정의하지 마세요. 그렇지 않으면 상황이 매우 혼란스러워질 것입니다. 일반적으로 말해서, 일상 생활을 따르는 한 문제를 일으킬 수 있는 심각한 상황은 발생하지 않습니다.
구문 분석을 위해 Internet Explorer의 innerText 및 innerHTML을 사용해 왔다면 elem() 함수를 사용해 볼 수 있습니다. 유사한 유틸리티를 구축하면 더욱 편리해지고 크로스 플랫폼 코드의 이점을 상속받을 수 있습니다. 이 두 가지 방법을 혼합하는 것은 매우 나쁩니다.
일부 유니코드 문자는 XML에 포함되지 않습니다. DOM을 구현하면 추가할 수 있지만 결과적으로 직렬화할 수 없습니다. 이러한 문자에는 대부분의 제어 문자와 유니코드 서로게이트 쌍의 개별 문자가 포함됩니다. 이는 문서에 이진 데이터를 포함하려고 할 경우에만 발생하지만 이는 또 다른 상황입니다.
결론
나는 DOM이 할 수 있는 일을 많이 다루었지만 DOM(및 JavaScript)이 할 수 있는 일이 훨씬 더 많습니다. 이러한 예제를 연구하고 탐색하여 클라이언트 스크립트, 템플릿 또는 특수 API가 필요할 수 있는 문제를 해결하는 데 어떻게 사용할 수 있는지 알아보세요.
DOM에는 한계와 단점이 있지만 많은 장점도 있습니다. Java 기술, Python 또는 JavaScript를 사용하든 동일한 방식으로 작동합니다. 위에서 언급한 템플릿을 사용하면 매우 쉽습니다. 사용하기 간단하고 강력합니다. Mozilla 기반 애플리케이션, OpenOffice 및 Blast Radius의 XMetaL을 포함하여 점점 더 많은 애플리케이션이 DOM을 지원하기 시작했습니다. 점점 더 많은 사양에 DOM이 필요하고 이를 확장(예: SVG)하므로 DOM은 항상 여러분 주변에 있습니다. 널리 배포된 이 도구를 사용하는 것이 현명할 것입니다.