打印

SD9024: 各浏览器中 DOM 接口的继承关系存在差异

作者:陆远

标准参考

DOM Core 及 DOM HTML 规范中明确说明了文档对象模型中各种接口的 IDL (Interface Definition Language) 定义,包括了接口之间的继承关系和接口所具有的属性及方法。
其中,DOM Core 规范定义了访问和操作文档对象的一套对象和接口,这其中包含了对 HTML 及 XML 的解析及操作;DOM HTML 规范为 DOM Core 的扩展,描述了 HTML 及 XHTML 的对象的细节。

如 HTMLDocument 接口将继承 Document 接口中的所有属性及方法,同时 HTMLDocument 接口也会实现一些自身的属性及方法。

下面的表格列举了 DOM Core 及 DOM HTML 规范中描述的接口的继承关系。其中  背景色  代表此接口属于 DOM Core 规范, 背景色  代表此接口属于 DOM HTML 规范。

1
is the root
2
inherits from 1
3
inherits from 2
4
inherits from 3
NodeList
NamedNodeMap
DOMImplementation
Node DocumentFragment
CharacterData Text CDATASection
Comment
Attr
DocumentType
Notation
Entity
EntityReference
ProcessingInstruction
Document HTMLDocument
Element HTMLElement HTMLHtmlElement
HTMLHeadElement
HTMLLinkElement
HTMLTitleElement
HTMLMetaElement
HTMLBaseElement
HTMLIsIndexElement
HTMLStyleElement
HTMLBodyElement
HTMLFormElement
HTMLSelectElement
HTMLOptGroupElement
HTMLOptionElement
HTMLInputElement
HTMLTextAreaElement
HTMLButtonElement
HTMLLabelElement
HTMLFieldSetElement
HTMLLegendElement
HTMLUListElement
HTMLOListElement
HTMLDListElement
HTMLDirectoryElement
HTMLMenuElement
HTMLLIElement
HTMLDivElement
HTMLParagraphElement
HTMLHeadingElement
HTMLQuoteElement
HTMLPreElement
HTMLBRElement
HTMLBaseFontElement
HTMLFontElement
HTMLHRElement
HTMLModElement
HTMLAnchorElement
HTMLImageElement
HTMLObjectElement
HTMLParamElement
HTMLAppletElement
HTMLMapElement
HTMLAreaElement
HTMLScriptElement
HTMLTableElement
HTMLTableCaptionElement
HTMLTableColElement
HTMLTableSectionElement
HTMLTableRowElement
HTMLTableCellElement
HTMLFrameSetElement
HTMLFrameElement
HTMLIFrameElement
HTMLCollection
HTMLOptionsCollection

关于 DOM Core 及 DOM HTML 中接口的更多信息,请参考 DOM 规范 Document Object Model CoreDocument Object Model HTML 中的内容。

问题描述

在 IE6 IE7 IE8(Q) 中,无法通过脚本检测到 DOM 接口原型,故无法获得其接口继承关系。
在 IE8(S) 中,只能检测到部分 DOM 接口原型,但这些原型不可枚举,故无法获得其接口继承关系。
在 Firefox 中,部分 DOM 接口原型不可枚举,接口继承关系与 DOM 规范中的描述不符。
在 Chrome Safari Opera 中,DOM 接口继承关系遵照了 DOM 规范中的描述。

造成的影响

若修改了某些较高层的 DOM 接口,如 Document 接口中的 getElementById 方法,则由于继承关系的存在,将会导致 Chrome Safari Opera 中作为 HTMLDocument 接口的实例——document 对象的 getElementById 方法异常。

受影响的浏览器

所有浏览器  

问题分析

分析以下代码:

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="d" style="display:none;">DIV element</div>
<script>
  try {
    Document.prototype.getElementById = function () { return "method getElementById in interface Document modified"; }
  } catch (ex) {
    document.write(ex.message + '<br />');
  }
  document.write('getElementById("d"): ' + document.getElementById("d") + '<br />');
  try {
    document.write('HTMLDocument.prototype.getElementById: ' + HTMLDocument.prototype.getElementById);
  } catch (ex) {
    document.write(ex.message + '<br />');
  }
</script>
</body>
</html>

上面代码中尝试修改了 Document 接口下的 getElementById 方法,之后检测了 document.getElementById("d") 的返回值以及 HTMLDocument.prototype.getElementById 方法的内容。

这段代码在不同浏览器中运行结果如下:

IE6 IE7 IE8(Q)
'Document' 未定义
getElementById("d"): [object]
'HTMLDocument' 未定义
IE8(S)
'Document' 未定义
getElementById("d"): [object HTMLDivElement]
HTMLDocument.prototype.getElementById: function getElementById() { [native code] }
Firefox
getElementById("d"): [object HTMLDivElement]
HTMLDocument.prototype.getElementById: function getElementById() { [native code] }
Chrome Safari Opera
getElementById("d"): method getElementById in interface Document modified
HTMLDocument.prototype.getElementById: function () { return "method getElementById in interface Document modified"; }

根据 DOM 规范,document 对象为 HTMLDocument 接口的实例,HTMLDocument 接口继承自 Document 接口,getElementById 方法定义在 Document 接口中。若通过 Document.prototype.getElementById 修改了 getElementById 方法,此时 HTMLDocument 接口中继承自 Document 接口的 getElementById 也会发生变化,则在通过 document.getElementById() 调用 getElementById 方法时将调用修改后的方法。

  • IE6 IE7 IE8(Q) 中,无法通过脚本获得 Document 及 HTMLDocument 接口原型,故无法修改 Document 接口内部的 getElementById 方法,所以 document.getElementById("d") 可以获得 id 属性为 "d" 的 HTMLDivElement 对象,;
  • IE8(S) 中,无法通过脚本获得 Document 接口原型,故无法修改 Document 接口内部的 getElementById 方法,所以 document.getElementById("d") 可以获得 id 属性为 "d" 的 HTMLDivElement 对象,通过 HTMLDocument.prototype.getElementById 可以看到 document 调用的 getElementById 方法实际上来自 HTMLDocument 接口,但这样并不能证明 HTMLDocument 接口与 Document 接口是否存在继承关系;
  • Firefox 中,修改 Document 接口下的 getElementById 方法后依然可以使用 document.getElementById("d") 获得 id 属性为 "d" 的 HTMLDivElement 对象。通过 HTMLDocument.prototype.getElementById 可以看到 document 调用的 getElementById 方法实际上来自 HTMLDocument 接口,可见 HTMLDocument 接口与 Document 接口没有继承关系;
  • Chrome Safari Opera 中,则是按照 DOM 规范的描述,HTMLDocument 接口继承自 Document 接口。

下面更加详细地测试各浏览器中 DOM 接口的继承关系,以 Node -> Document -> HTMLDocument -> 以及 Node -> Element -> HTMLElement -> HTMLFormElement 为例。

<!DOCTYPE html>
<html>
<head>
</head>
<body style="font:24px Consolas;">
<script>
  function $r(str) { document.write(str); }
  function testDOMObject(objStr, level) {
    var indent = '', g = 0, s = [];
    var T = '<strong style="background-color:lime">true</strong>';
    var F = '<strong style="background-color:tomato">false</strong>';
    var NA = '<strong style="background-color:fuchsia">N/A</strong>';
    while (g++ < level) { indent += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'; }
    try {
      var o = eval(objStr);
      s.push(indent + '<strong style="background-color:greenyellow">Interface ' + objStr +': ' + o + '</strong>');
      s.push('<br />');
      try {
        s.push(indent + objStr + '.prototype: ' + o.prototype);
      } catch (ex) {
        s.push(indent + '<strong style="background-color:coral">' + objStr + '.prototype: ' + ex.message + '</strong>');
      }
      s.push('<br />');
      s.push(indent + objStr + '.prototype.hasOwnProperty("XXX"): <br />');
      var str = [];
      try {
        for (var i in o.prototype) {
          str.push(indent + '<em>' + i + '</em>: ' + ((o.prototype.hasOwnProperty(i)) ? T : F) + '<br />');
        }
      } catch (ex) { }
      s.push(((str.length > 0) ? str.join('') : indent + NA));
    } catch (ex) {
      s.push(indent + '<strong style="background-color:coral">Interface ' + objStr + ': ' + ex.message + '</strong>');
    }
    s.push('<br /><br />');
    return s.join('');
  }
  var objHTMLDocument = ['Node', 'Document', 'HTMLDocument'];
  var objHTMLFormElement = ['Node', 'Element', 'HTMLElement', 'HTMLFormElement'];
  for (var i in objHTMLDocument) {
    $r(testDOMObject(objHTMLDocument[i], i));
  }
  for (var i in objHTMLFormElement) {
    $r(testDOMObject(objHTMLFormElement[i], i));
  }
</script>
</body>
</html>

在各浏览器中运行上面代码之后可以得到:

  IE6 IE7 IE8(Q) IE8(S) Firefox Chrome Safari Opera
Node 接口 No No Yes Yes
  Document 接口 No No Yes Yes
  Document 接口继承自 Node 接口 No No No Yes
    HTMLDocument 接口 No Yes Yes Yes
    HTMLDocument 接口继承自 Document 接口 No No No Yes
  Element 接口 No Yes Yes Yes
  Element 接口继承自 Node 接口 No No No Yes
    HTMLElement 接口 No No Yes Yes
    HTMLElement 接口继承自 Element 接口 No No Yes Yes
      HTMLFormElement 接口 No Yes Yes Yes
      HTMLFormElement 接口继承自 HTMLElement 接口 No No No Yes

可见只有 Chrome Safari Opera 中,DOM 接口的继承关系遵照了 DOM 规范中的描述。

解决方案

由于各浏览器中 DOM 接口实现的不一致性,这里建议应尽量避免修改浏览器脚本引擎内 DOM 接口的原生方法。

参见

知识库

相关问题

测试环境

操作系统版本: Windows 7 Ultimate build 7600
浏览器版本: IE6
IE7
IE8
Firefox 3.6.9
Chrome 7.0.517.0 dev
Safari 5.0.1
Opera 10.61
测试页面: get.html
inherit.html
本文更新时间: 2010-09-08

关键字

DOM Core HTML Document HTMLDocument Element interface inherit