>  기사  >  백엔드 개발  >  XML의 중요성: DOM 너머(DOM을 쉽게 사용하기 위한 팁과 요령)

XML의 중요성: DOM 너머(DOM을 쉽게 사용하기 위한 팁과 요령)

黄舟
黄舟원래의
2017-02-27 16:30:061361검색

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. 이전

function insertAfter(parent, node, referenceNode) {
    if(referenceNode.nextSibling) {
        parent.insertBefore(node, referenceNode.nextSibling);
    } else {
        parent.appendChild(node);
    }
}
function prependChild(parent, node) {
    if (parent.firstChild) {
        parent.insertBefore(node, parent.firstChild);
    } else {
        parent.appendChild(node);
    }
}


에 의한 삽입 및 추가 방법이 잘못되었습니다. 실제로는 목록 1과 같습니다. 에서는 참조 노드가 비어 있을 때 AppendChild()로 반환되도록 insertBefore() 함수를 정의했습니다. 따라서 위의 방법을 사용하는 대신 목록 2의 방법을 사용하거나 이를 건너뛰고 내장 함수만 사용할 수 있습니다.

목록 2. 이전 항목으로 삽입하고 추가하는 올바른 방법

function insertAfter(parent, node, referenceNode) {
    parent.insertBefore(node, referenceNode.nextSibling);
}
function prependChild(parent, node) {
    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['Node']) {
    window.Node = new Object();
    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. 내부 텍스트

function innerText(node) {
    // is this a text or CDATA node?
    if (node.nodeType == 3 || node.nodeType == 4) {
        return node.data;
    }
    var i;
    var returnValue = [];
    for (i = 0; i < node.childNodes.length; i++) {
        returnValue.push(innerText(node.childNodes[i]));
    }
    return returnValue.join(&#39;&#39;);
}


바로가기

사람들은 종종 DOM이 너무 장황하고 간단한 기능에는 많은 코드가 필요하다고 불평합니다. . 예를 들어, 텍스트를 포함하고 버튼 클릭에 응답하는 dc6dce4a544fdca2df29d5ac0ea9906b 요소를 생성하려는 경우 코드는 다음과 같습니다.

목록 5. dc6dce4a544fdca2df29d5ac0ea9906b 🎜>
아아아아



    若频繁按照这种方式创建节点,键入所有这些代码会使您很快疲惫不堪。必须有更好的解决方案 —— 确实有这样的解决方案!下面这个实用工具可以帮助您创建元素、设置元素属性和风格,并添加文本子节点。除了 name 参数,其他参数都是可选的。

清单 6. 函数 elem() 快捷方式

function elem(name, attrs, style, text) {
    var e = document.createElement(name);
    if (attrs) {
        for (key in attrs) {
            if (key == &#39;class&#39;) {
                e.className = attrs[key];
            } else if (key == &#39;id&#39;) {
                e.id = attrs[key];
            } else {
                e.setAttribute(key, attrs[key]);
            }
        }
    }
    if (style) {
        for (key in style) {
            e.style[key] = style[key];
        }
    }
    if (text) {
        e.appendChild(document.createTextNode(text));
    }
    return e;
}

 
    使用该快捷方式,您能够以更加简洁的方法创建 清单 5 中的 dc6dce4a544fdca2df29d5ac0ea9906b 元素。注意,attrs 和 style 参数是使用 Javascript 文本对象而给出的。

清单 7. 创建 dc6dce4a544fdca2df29d5ac0ea9906b 的简便方法

function handle_button() {
    var parent = document.getElementById(&#39;myContainer&#39;);
    parent.appendChild(elem(&#39;div&#39;,
      {class: &#39;myDivCSSClass&#39;, id: &#39;myDivId&#39;}
      {position: &#39;absolute&#39;, left: &#39;300px&#39;, top: &#39;200px&#39;},
      &#39;This is the first text of the rest of this code&#39;));
}

    在您想要快速创建大量复杂的 DHTML 对象时,这种实用工具可以节省您大量的时间。模式在这里就是指,如果您有一种需要频繁创建的特定的 DOM 结构,则使用实用工具来创建它们。这不但减少了您编写的代码量,而且也减少了重复的剪切、粘贴代码(错误的罪魁祸首),并且在阅读代码时思路更加清晰。
    
    接下来是什么?
    DOM 通常很难告诉您,按照文档的顺序,下一个节点是什么。下面有一些实用工具,可以帮助您在节点间前后移动:

清单 8. nextNode 和 prevNode

// return next node in document order
function nextNode(node) {
    if (!node) return null;
    if (node.firstChild){
        return node.firstChild;
    } else {
        return nextWide(node);
    }
}
// helper function for nextNode()
function nextWide(node) {
    if (!node) return null;
    if (node.nextSibling) {
        return node.nextSibling;
    } else {
        return nextWide(node.parentNode);
    }
}
// return previous node in document order
function prevNode(node) {
    if (!node) return null;
    if (node.previousSibling) {
      return previousDeep(node.previousSibling);
    }
    return node.parentNode;
}
// helper function for prevNode()
function previousDeep(node) {
    if (!node) return null;
    while (node.childNodes.length) {
        node = node.lastChild;
    }
    return node;
}

 


    轻松使用 DOM
    有时候,您可能想要遍历 DOM,在每个节点调用函数或从每个节点返回一个值。实际上,由于这些想法非常具有普遍性,所以 DOM Level 2 已经包含了一个称为 DOM Traversal and Range 的扩展(为迭代 DOM 所有节点定义了对象和 API),它用来为 DOM 中的所有节点应用函数和在 DOM 中选择一个范围。因为这些函数没有在 Internet Explorer 中定义(至少目前是这样),所以您可以使用 nextNode() 来做一些
类似的事情。

    在这里,我们的想法是创建一些简单、普通的工具,然后以不同的方式组装它们来达到预期的效果。如果您很熟悉函数式编程,这看起来会很亲切。Beyond JS 库(参阅 参考资料)将此理念发扬光大。

清单 9. 函数式 DOM 实用工具

// return an Array of all nodes, starting at startNode and
// continuing through the rest of the DOM tree
function listNodes(startNode) {
    var list = new Array();
    var node = startNode;
    while(node) {
        list.push(node);
        node = nextNode(node);
    }
    return list;
}
// The same as listNodes(), but works backwards from startNode.
// Note that this is not the same as running listNodes() and
// reversing the list.
function listNodesReversed(startNode) {
    var list = new Array();
    var node = startNode;
    while(node) {
        list.push(node);
        node = prevNode(node);
    }
    return list;
}
// apply func to each node in nodeList, return new list of results
function map(list, func) {
    var result_list = new Array();
    for (var i = 0; i < list.length; i++) {
        result_list.push(func(list[i]));
    }
    return result_list;
}
// apply test to each node, return a new list of nodes for which
// test(node) returns true
function filter(list, test) {
    var result_list = new Array();
    for (var i = 0; i < list.length; i++) {
        if (test(list[i])) result_list.push(list[i]);
    }
    return result_list;
}

 

    清单 9 包含了 4 个基本工具。listNodes() 和 listNodesReversed() 函数可以扩展到一个可选的长度,这与 Array 的 slice() 方法效果类似,我把这个作为留给您的练习。另一个需要注意的是,map() 和 filter() 函数是完全通用的,用于处理任何 列表(不只是节点列表)。现在,我向您展示它们的几种组合方式。

清单 10. 使用函数式实用工具

// A list of all the element names in document order
function isElement(node) {
    return node.nodeType == Node.ELEMENT_NODE;
}
function nodeName(node) {
    return node.nodeName;
}
var elementNames = map(filter(listNodes(document),isElement), nodeName);
// All the text from the document (ignores CDATA)
function isText(node) {
    return node.nodeType == Node.TEXT_NODE;
}
function nodeValue(node) {
    return node.nodeValue;
}
var allText = map(filter(listNodes(document), isText), nodeValue);

 

    您可以使用这些实用工具来提取 ID、修改样式、找到某种节点并移除,等等。一旦 DOM Traversal and Range API 被广泛实现,您无需首先构建列表,就可以用它们修改 DOM 树。它们不但功能强大,并且工作方式也与我在上面所强调的方式类似。

    DOM 的危险地带
    注意,核心 DOM API 并不能使您将 XML 数据解析到 DOM,或者将 DOM 序列化为 XML。这些功能都定义在 DOM Level 3 的扩展部分“Load and Save”,但它们还没有被完全实现,因此现在不要考虑这些。每个平台(浏览器或其他专业 DOM 应用程序)有自己在 DOM 和 XML间转换的方法,但跨平台转换不在本文讨论范围之内。

    DOM 并不是十分安全的工具 —— 特别是使用 DOM API 创建不能作为 XML 序列化的树时。绝对不要在同一个程序中混合使用 DOM1 非名称空间 API 和 DOM2 名称空间感知的 API(例如,createElement 和 createElementNS)。如果您使用名称空间,请尽量在根元素位置声明所有名称空间,并且不要覆盖名称空间前缀,否则情况会非常混乱。一般来说,只要按照惯例,就不会触发使您陷入麻烦的临界情况。

    如果您一直使用 Internet Explorer 的 innerText 和 innerHTML 进行解析,那么您可以试试使用 elem() 函数。通过构建类似的一些实用工具,您会得到更多便利,并且继承了跨平台代码的优越性。将这两种方法混合使用是非常糟糕的。

    某些 Unicode 字符并没有包含在 XML 中。DOM 的实现使您可以添加它们,但后果是无法序列化。这些字符包括大多数的控制字符和Unicode 代理对(surrogate pair)中的单个字符。只有您试图在文档中包含二进制数据时才会遇到这种情况,但这是另一种转向(gotcha)情况。


결론
DOM이 할 수 있는 일을 많이 다루었지만 DOM(및 JavaScript)이 할 수 있는 일이 훨씬 더 많습니다. 이러한 예제를 연구하고 탐색하여 클라이언트 스크립트, 템플릿 또는 특수 API가 필요할 수 있는 문제를 해결하는 데 어떻게 사용할 수 있는지 알아보세요.

DOM에는 고유한 한계와 단점이 있지만 많은 장점도 있습니다. Java 기술, Python 또는 JavaScript를 사용하더라도 동일한 방식으로 작동하며 SAX를 사용하는 것이 매우 쉽습니다. 위의 템플릿을 사용하면 사용이 간단하면서도 강력합니다. Mozilla 기반 애플리케이션, OpenOffice 및 Blast Radius의 XMetaL을 포함하여 점점 더 많은 애플리케이션이 DOM을 지원하기 시작했습니다. 점점 더 많은 사양이 DOM(예: SVG)을 요구하고 확장하므로 DOM은 항상 우리 주변에 있습니다. 널리 배포된 이 도구를 사용하는 것이 현명할 것입니다.

위 내용은 XML 질문: DOM 너머(DOM을 쉽게 사용하기 위한 팁과 요령)의 내용입니다. 더 많은 관련 내용은 PHP 중국어 웹사이트(www.php.cn)를 참고하세요!


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.