新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论DOM, SAX, XPath等。
    [返回] 计算机科学论坛XML.ORG.CN讨论区 - XML技术『 DOM/SAX/XPath 』 → 轻松使用 DOM 的技巧和诀窍 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 3440 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: 轻松使用 DOM 的技巧和诀窍 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     菜籽 帅哥哟,离线,有人找我吗?双鱼座1981-2-28
      
      
      威望:5
      头衔:软件民工
      等级:研二(Sowa的知识表示写得真好!)
      文章:875
      积分:5655
      门派:XML.ORG.CN
      注册:2004/7/25

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给菜籽发送一个短消息 把菜籽加入好友 查看菜籽的个人资料 搜索菜籽在『 DOM/SAX/XPath 』的所有贴子 点击这里发送电邮给菜籽 引用回复这个贴子 回复这个贴子 查看菜籽的博客楼主
    发贴心情 轻松使用 DOM 的技巧和诀窍

    (转贴来自ibm网站)
    Dethe Elza
    高级技术架构师, Blast Radius
    2005 年 6 月 16 日

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

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

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

    insertAfter 和 prependChild
    第一个诀窍就是“没有诀窍”。DOM 有两种方法将孩子节点添加到容器节点(常常是一个 Element,也可能是一个 Document 或 DocumentFragment):appendChild(node) 和 insertBefore(node, referenceNode)。看起来似乎缺少了什么。假如我想在一个参考节点后面插入或者由前新增(prepend)一个子节点(使新节点位于列表中的第一位),我该怎么做呢?很多年以来,我的解决方法是编写下列函数:

    清单 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 一样,insertBefore() 函数已经被定义为,在参考节点为空时返回到 appendChild()。因此,您可以不使用上面的方法,而使用 清单 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 做什么?
    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 太过冗长,并且简单的功能也需要编写大量代码。例如,如果您想创建一个包含文本并响应点击按钮的 <div> 元素,代码可能类似于:

    清单 5. 创建 <div> 的“漫长之路”

    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 中的 <div> 元素。注意,attrs 和 style 参数是使用 JavaScript 文本对象而给出的。

    清单 7. 创建 <div> 的简便方法

    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 对象时,这种实用工具可以节省您大量的时间。模式在这里就是指,如果您有一种需要频繁创建的特定的 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;使用上述的模板,它使用起来既简洁又强大。越来越多的应用程序开始支持 DOM,这包括基于 Mozilla 的应用程序、OpenOffice 和 Blast Radius 的 XMetaL。越来越多的规范需要 DOM,并对它加以扩展(例如,SVG),因此 DOM 时时刻刻就在您的身边。使用这种被广泛部署的工具,绝对是您的明智之举。


    [此贴子已经被作者于2005-7-15 21:47:52编辑过]

       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    重拾英语...

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2005/7/15 12:02:00
     
     binaryluo 帅哥哟,离线,有人找我吗?
      
      
      威望:6
      等级:研二(Pi-Calculus看得一头雾水)(版主)
      文章:679
      积分:5543
      门派:IEEE.ORG.CN
      注册:2005/2/19

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给binaryluo发送一个短消息 把binaryluo加入好友 查看binaryluo的个人资料 搜索binaryluo在『 DOM/SAX/XPath 』的所有贴子 引用回复这个贴子 回复这个贴子 查看binaryluo的博客2
    发贴心情 
    up
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2005/7/21 23:57:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 DOM/SAX/XPath 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2025/6/22 10:22:17

    本主题贴数2,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    78.125ms