唐山网站建设

设为主页 加入收藏 繁體中文

XML 题目: 超出DOM(轻松使用 DOM 的技能和窍门)

核心提示:XML 题目: 超出DOM(轻松使用 DOM 的技能和窍门) ,浏览XML 题目: 超出DOM(轻松使用 DOM 的技能和窍门) ,Dethe Elza (delza@livingcode.org), 高级技术架构师, Blast Radius     文档对象模型(Document Object Model,DOM)是用于操纵 XML 和 HTML

Dethe Elza (delza@livingcode.org), 高级技术架构师, Blast Radius

 
    文档对象模型(Document Object Model,DOM)是用于操纵 XML 和 HTML 数据的最常常使用工具之1,但是它的潜力却很少被充分发掘出来。通过利用 DOM 的上风,并使它更加易用,您将取得1款利用于 XML 利用程序(包括动态 Web 利用程序)的强大工具。

    本期文章先容了1位客串的专栏作家,同时也是我的朋友和同事 Dethe Elza。Dethe 在利用 XML 进行 Web 利用程序开发方面经验丰富,在此,我要感谢他对我在先容使用 DOM 和 ECMAScript 进行 XML 编程这1方面的帮助。请密切关注本专栏,以了解 Dethe 的更多专栏文章。
 —— David Mertz

    DOM 是处理 XML 和 HTML 的标准 API 之1。由于它占用内存大、速度慢,并且冗杂,所以常常遭到人们的指责。虽然如此,对很多利用程序来讲,它依然是最好选择,而且比 XML 的另1个主要 API —— SAX 无疑要简单很多。DOM 正逐渐出现在1些工具中,比如 Web 浏览器、SVG 浏览器、OpenOffice,等等。

    DOM 很好,由于它是1种标准,并且被广泛地实现,同时也内置到其他标准中。作为标准,它对数据的处理与编程语言无关(这多是优点,也多是缺点,但最少使我们处理数据的方式变得1致)。DOM 现在不但内置于 Web 浏览器,而且一样成为很多基于 XML 的规范的1部份。既然它已成为您的工具的1部份,并且或许您偶然还会使用它,我想现在应当充分利用它给我们带来的功能了。

    在使用 DOM 1段时间后,您会看到构成了1些模式 —— 您想要反复做的事情。快捷方式可以帮助您处理冗杂的 DOM,并创建自解释的、优雅的代码。这里搜集了1些我常常使用的技能和窍门,还有1些 JavaScript 示例。

insertAfter 和 prependChild

    第1个窍门就是“没有窍门”。DOM 有两种方法将孩子节点添加到容器节点(常常是1个 Element,也多是1个 Document 或 Document Fragment):appendChild(node) 和 insertBefore(node, referenceNode)。看起来仿佛缺少了甚么。假设我想在1个参考节点后面插进或由前新增(prepend)1个子节点(使新节点位于列表中的第1位),我该怎样做呢?很多年以来,我的解决方法是编写以下函数:

清单 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 1样,insertBefore() 函数已被定义为,在参考节点为空时返回到 appendChild()。因此,您可以不使用上面的方法,而使用 清单 2 中的方法,或跳过它们仅使用内置函数:

清单 2. 插进和由前新增的正确方法
function insertAfter(parent, node, referenceNode) {
    parent.insertBefore(node, referenceNode.nextSibling);
}
function prependChild(parent, node) {
    parent.insertBefore(node, parent.firstChild);
}
 
    假设您刚刚接触 DOM 编程,有必要指出的是,固然您可使多个指针指向1个节点,但该节点只能存在于 DOM 树中的1个位置。因此,假设您想将它插进到树中,没必要先将它从树中移除,由于它会自动被移除。当重新将节点排序时,这类机制很方便,仅需将节点插进到新位置即可。

    根据这类机制,若想交换两个相邻节点(称为 node1 和 node2)的位置,可使用以下方案之1:

node1.parentNode.insertBefore(node2, node1);

node1.parentNode.insertBefore(node1.nextSibling, node1);

    还可使用 DOM 做甚么?

    Web 页面中大量利用了 DOM。若访问 bookmarklets 站点(参阅 参考资料),您会发现很多有创意的简短脚本,它们可以重新编排页面,提取链接,隐躲图片或 Flash 广告,等等。

    但是,由于 Internet Explorer 没有定义 Node 接口常量(可以用于辨认节点类型),所以您必须确保在遗漏接口常量时,首先为 Web 在 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('');
}
 

    快捷方式

    人们常常抱怨 DOM 太过冗杂,并且简单的功能也需要编写大量代码。例如,假设您想创建1个包括文本并响应点击按钮的

元素,代码可能类似于:

清单 5. 创建

的“漫长之路”
function handle_button() {
    var parent = document.getElementById('myContainer');
    var div = document.createElement('div');
    div.className = 'myDivCSSClass';
    div.id = 'myDivId';
    div.style.position = 'absolute';
    div.style.left = '300px';
    div.style.top = '200px';
    var text = "This is the first text of the rest of this code";
    var textNode = document.createTextNode(text);
    div.appendChild(textNode);
    parent.appendChild(div);
}
 


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

清单 6. 函数 elem() 快捷方式
function elem(name, attrs, style, text) {
    var e = document.createElement(name);
    if (attrs) {
        for (key in attrs) {
            if (key == 'class') {
                e.className = attrs[key];
            } else if (key == 'id') {
                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 中的

元素。留意,attrs 和 style 参数是使用 JavaScript 文本对象而给出的。

清单 7. 创建

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

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

清单 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,在每个节点调用函数或从每个节点返回1个值。实际上,由于这些想法非常具有普遍性,所以 DOM Level 2 已包括了1个称为 DOM Traversal and Range 的扩大(为迭代 DOM 所有节点定义了对象和 API),它用来为 DOM 中的所有节点利用函数和在 DOM 当选择1个范围。由于这些函数没有在 Internet Explorer 中定义(最少目前是这样),所以您可使用 nextNode() 来做1些
类似的事情。

    在这里,我们的想法是创建1些简单、普通的工具,然后以不同的方式组装它们来到达预期的效果。假设您很熟习函数式编程,这看起来会很亲切。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() 函数可以扩大到1个可选的长度,这与 Array 的 slice() 方法效果类似,我把这个作为留给您的练习。另1个需要留意的是,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、修改样式、找到某种节点并移除,等等。1旦 DOM Traversal and Range API 被广泛实现,您无需首先构建列表,便可以够用它们修改 DOM 树。它们不但功能强大,并且工作方式也与我在上面所夸大的方式类似。

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

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

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

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


    结束语
    我已先容了 DOM 能做的很多事情,但是 DOM(和 JavaScript)可以做的事情远不止这些。仔细研究、揣摩这些例子,看看是如何使用它们来解决可能需要客户端脚本、模板或专用 API 的题目。

    DOM 有自己的局限性和缺点,但同时也具有众多优点:它内置于很多利用程序中;不管使用 Java 技术、Python 或 JavaScript,它都以相同方式工作;它非常便于使用 SAX;使用上述的模板,它使用起来既简洁又强大。越来越多的利用程序开始支持 DOM,这包括基于 Mozilla的利用程序、OpenOffice 和 Blast Radius 的 XMetaL。越来越多的规范需要 DOM,并对它加以扩大(例如,SVG),因此 DOM 时时刻刻就在您的身旁。使用这类被广泛部署的工具,尽对是您的明智之举。

http://www.fw8.net/
TAG:方法,函数,节点,清单,实用工具
评论加载中...
内容:
评论者: 验证码: