XML 问题: 超越DOM(轻松使用 DOM 的技巧和诀窍)

马克西姆1010

马克西姆1010

2016-02-19 21:45

生活已是百般艰难,为何不努力一点。下面图老师就给大家分享XML 问题: 超越DOM(轻松使用 DOM 的技巧和诀窍),希望可以让热爱学习的朋友们体会到设计的小小的乐趣。

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

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

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

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

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

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

  insertAfter 和 prependChild

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

(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/webkaifa/)

  清单 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)的位置,可以使用下列方案之一:

(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/webkaifa/)

  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 时时刻刻就在您的身边。使用这种被广泛部署的工具,绝对是您的明智之举。

展开更多 50%)
分享

猜你喜欢

XML 问题: 超越DOM(轻松使用 DOM 的技巧和诀窍)

Web开发
XML 问题: 超越DOM(轻松使用 DOM 的技巧和诀窍)

使用DOM创建XML

Web开发
使用DOM创建XML

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

使用XML DOM生成XML(3)

Web开发
使用XML DOM生成XML(3)

使用XML DOM生成XML(4)

Web开发
使用XML DOM生成XML(4)

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

使用XML DOM生成XML(5)

Web开发
使用XML DOM生成XML(5)

使用XML DOM生成XML(1)

Web开发
使用XML DOM生成XML(1)

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

VB.NET实现窗体图标最小化到状态栏

VB.NET实现窗体图标最小化到状态栏

VC程序界面多模式显示

VC程序界面多模式显示
下拉加载更多内容 ↓