西红柿爱番茄

Feed Rss

浏览器自动补全html、head、body引用,但是……

06.16.2010, Javascript, by .

2010-6-18 updates:

surfing的时候发现了个非常不错的关于浏览器如何构建DOM树的工具:Live DOM Viewer。通过它可以非常直观的测试不同浏览器对解析HTML内容的差异。

2010-6-17 updates:

在跟番茄讨论这个问题的时候,他举例用CSS的样式来证明了body元素也会自动渲染的事实,这点本人没有想到,也没有经过测试就说了下面的文章(内容已经修改过了),惭愧……但是还存在这样的一个问题:就如下面的出现的情况,如果页面没有声明body标签的时候,为什么CSS可以渲染body的样式,而javascrpt无法直接调用document.body的引用?《测试用例

经过一番测试和狂搜,大概明白了其中的原因,这需要了解javascrpt解析引擎和CSS渲染引擎是在什么时候介入页面起作用了。在浏览器解析HTML并生成DOM树的时候,是按从上至下的顺序解析的,浏览器最先会生成DOM树的根节点(HTMLDocument)和HTMLHEADElement(不知可否这样说),不管页面有没有声明html和head标签都会自动最先生成。之后再往下解析HTML标签,如果在遇到body标签(或者布局性标签)之前先遇到script标签的时候,就会在DOM树中生成该节点,但是这时这个script节点是head元素的子元素,而且这时候javascrpt解析引擎就会执行其中的代码(如果此时页面还没有解析到body的时候,或者还没有解析到如下面所说的布局性的标签的时候,DOM树中还不存在body节点,调用document.body就会出错),如果在body(或者其他布局性标签)之前,解析到link或者style标签,也会生成相应的节点,也是head的子节点,但是CSS渲染引擎并不会立即渲染页面的样式,它会在DOM树解析完成的时候才开始渲染页面,这就是上面的问题中所说的情况:CSS可以渲染body,而JS无法调用body的引用。《测试用例》,用firebug查看该实例生成的HTML结构。

所以,应该记住一点:如果在页面中使用script标签外联的js文件或者是行内脚本,当浏览器解析HTML内容的过程中遇到script标签的话,就会立即加载并且执行script的代码,之后才会继续解析后续的HTML的内容,除非是通过异步、动态加载js文件或者给script标签加上了defer、async属性。

但是如果在script或者link、style标签之前,有布局性的标签的时候,这就有两种情况了:没有文本内容的布局性标签和有文本内容的布局性标签。

  1. 当没有文本内容的时候。Chrome、Safari、Opera会将布局性标签下面的link、style、script等标签也渲染到body元素里(自动补全了该元素),这样通过script获取document.body的引用就没问题了。可是Firefox、IE会将link、style、script等标签都渲染到head元素里,先于body元素先在DOM树中生成,这样在js代码里直接获取document.body的引用就会出错。《测试用例
  2. 当有文本内容的时候。各个浏览器都显示一致了。都会把link、style、script都渲染到body里,这样body元素就先于link、style、script标签等先在DOM树中生成,获取body的引用就没问题了。《测试用例

============================================================================

正如题目中所说,一个正常的包含html、head、body的标签是不会出现这个问题的,页面都有声明了,浏览器它就省事多了。但是当页面没有html、head、body标签的时候,浏览器就有点发狂了,它惩罚偷懒的人的时候到咯。

在开始叙述之前,先来测试几个在不同的条件下的样例(这个测试主要是针对下面将要叙述的关于动态加载js来说的),下面将会提到一个暂时不知道怎么表达的词,就是我说的“布局性的标签”,它主要是指p、span、div等这些具有布局性质的元素,因此script、link、style等就不是了。

  1. 当页面无html、head、body标签,而且也没有其他的布局性的标签,只有一个script:《测试用例》。document.body出错了
  2. 当页面无html、head、body标签,有布局性的标签,而且带有文本内容(比如:<p>hello</p>,嵌套的空标签也不行),当这些标签在当前script标签前面的时候:《测试用例》。可以获得document.body的引用,它变好了。但是如果前面不是布局性的元素,或者是嵌套没有文本内容的布局性元素,《前面是link元素》,《嵌套空布局性元素》,在空布局性标签的情况下,IE、Firefox都挂了,chrome,safari,opera正常。前面是link元素的话全挂鸟。
  3. 当页面无html、head、body标签,有布局性的标签,而且不管有没有内容,当这些标签在当前script标签后面的时候:《测试用例》。document.body出错,它纠结了。
  4. 跟上面相同的条件,document.documentElement和document.getElementsByTagName(“head”)都可以获得引用:《documentElement》,《document.getElementsByTagName(“head”)

通过上面各种条件的测试,可以得出这样的结论:当页面中没有声明html、head、body标签的时候,也没有其他的布局性的标签(除了script、style、link等),各个浏览器在DOM文档树中会自动补全html、head、body的引用,ie还会自动补全title标签,后续的js代码可以获得html、head元素的引用,但是得不到body的引用。如果包含了布局性的元素,而且这些元素包含文本内容(从兼容性出发),后续的js代码可以获得document.body的引用。但是如果布局性的元素在js代码之后,则在前面的js代码中无法获得body标签的引用。

根据浏览器的这一个特性,对上面所说的条件可以再缩小化的:当没有声明body标签的时候,也都会出现上面所说的无法直接得到document.body引用的问题。《测试用例

因此,不管页面中有没有声明html、head标签,各个浏览器(连IE6都fuck能这么做了)都会在DOM文档树中自动补全html、head、body元素的引用,而操作body则需要小心点了,防止出现上面测试用例中提到的无法获得document.body引用的失误。

从这点出发,联想到Steve Souders在它的一篇博文中《appendChild vs insertBefore》提到的在动态插入js文件的时候,如果页面中缺少head、body的标签的情况,是该用appendChild还是insertBefore的比较。根据上面的测试,页面不管有没有head声明,都会自动补全head元素的引用,所以使用下面的方式都是能够适用于大部分场合的:

[javascript]
var script = document.createElement("script");
script.setAttribute("src","/wp-content/j/combine_js.js.php");
document.getElementsByTagName("head")[0].appendChild(script);
[/javascript]
不过jQuery的实现方式是把document.documentElement也考虑在内了,并且使用了insertBefore的方式:《测试用例
[javascript]
head = document.getElementsByTagName ("head")[0] ||
document.documentElement;
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore(script, head.firstChild);
[/javascript]

jQuery能用head元素在它的firstChild之前插入script标签的技巧是借助于在上面出现的情况中,IE下会自动补全head元素,并且还会自动补全title元素,而FF等浏览器下则是利用了空节点作为firstChild。而对于它所说的IE6的bug,我估摸不透。但是浏览器会自动补全head元素,所以再使用document.documentElement,也就觉得有点多此一举了。

但最重要的是,人不要那么懒啦,声明一个完整的HTML页面吧,这样浏览器会亲死你~

浏览器自动补全html、head、body引用,但是…… 有 3 条回应

  1. 这个问题真怪异. 顶西红柿

  2. 2010-06-17 在 22:20 www.zgh.gov.cn

    写的不错,嘿嘿.楼主模板不错,有空我也搞个去

  3. 2010-06-18 在 09:28 深西陈

    这个要慢慢消化。。

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>