IE 的浏览器模式和文本模式(二)

一年半之前我写了一篇《关于浏览器模式和文本模式的困惑》,介绍了 IE8+ 特有的浏览器模式(Browser Mode)和文本模式(Document Mode),以及我的测试和微软文档有出入的部分。其中有一些没有提到的内容,本文继续讨论。

判断真正的 IE 版本

很多 JS 框架都通过 UA 判断 IE 的版本。对于 IE6,这种做法没问题( IE6 没有浏览器模式的概念,也没有其它 IE 可以把浏览器模式改为 IE6;IE7 虽然也没有浏览器模式,但 IE8+ 可以把浏览器模式设置为 IE7 模式)。但是从 IE8 开始引入的浏览器模式会产生不同的 UA。例如,IE9 有这些:

浏览器模式navigator.userAgent默认文本模式
IE7 MSIE 7.0 IE7标准
IE8 MSIE 8.0 && Trident/4.0 IE8标准
IE9 MSIE 9.0 && Trident/5.0 IE9标准
IE9兼容性 MSIE 7.0 && Trident/5.0 IE7标准

如果仅通过 UA 中的「MSIE X.0」来判断,会得到 IE7~9 三种不同结果。

实际上,对于 IE8+,根据 UA 字符串只能确定当前是否是兼容性视图。因为兼容性视图的 UA 中,IE 版本和 Trident 版本不匹配。例如 UA 里同时有「MSIE 7.0」和「Trident/6.0」,说明浏览器模式肯定是 IE10兼容性。这是因为 IE8 才开始给 UA 加上 Trident 信息,而 Trident/6.0 是 IE10 所特有。

除此之外,上面 IE7 IE8 这两种浏览器模式,UA 和跟真正的 IE7 或 IE8 没有任何区别,根据 UA 完全没办法区分。甚至连 IE9 模式,我们也无法确认这是 IE9 浏览器的默认模式,还是 IE10 浏览器的 IE9 模式。

下面来看看文本模式,依然用 IE9 测试。选择不同的文本模式,documentMode 的值也不一样。

文本模式document.documentMode
IE7标准 7
IE8标准 8
IE9标准 9
IE5怪异(Quirks) 5

document.documentMode 这个 JS 属性是 IE8 引入的,对于 IE8+ 无论选择什么文本模式,这个属性都有值。而 IE6 和 IE7 下,这个属性是 undefined。根据这一点,可以结合 UA 判断出用户使用的是不是真正的 IE7:UA 包含 IE7 时,如果 documentMode 等于 undefined,就一定是真正的 IE7 浏览器。对于 IE8+,这种方法就力不从心。例如 IE10 在浏览器模式为 IE8,文本模式为 IE8标准 时,与真正的 IE8 比较,无论是 UA,还是 document.documentMode,都一模一样。

综上,我们可以通过检查 UA 中 Trident 版本和 IE 版本是否匹配,来判断浏览器是否工作在兼容性视图模式下。结合 document.documentMode,还可以判断出用户是否使用真正的 IE7 浏览器。

JScript 引擎版本号

JScript 是 IE 的 JS 引擎,IE 提供了一系列 JS 接口来获取它的 JScript 信息:

函数返回值
ScriptEngine() JS 中固定返回「JScript」
ScriptEngineMajorVersion() 大版本号
ScriptEngineMinorVersion() 小版本号
ScriptEngineBuildVersion() 内部版本号

我用这些接口测试了 IE6~10,发现 JScript 的版本号只与浏览器有关,与浏览器的浏览器模式或文档模式无关。

浏览器JScript 版本号
IE6 5.6.8827
IE7 5.7.22145
IE8 5.8.18702
IE9 9.0.16434
IE10 10.0.16521

忽略内部版本号,只关注前两个数字,我们会发现,从 IE9 把 JS 引擎换成 Chakra 开始,版本号的规律变了。令人欣慰的是,不同的 JScript 版本号对应着不同版本的浏览器,这对判断出真正的 IE 版本很有帮助。例如,要识别低于 IE8 的浏览器,下面这样写就可以了。

if(ScriptEngineMinorVersion() != 0 && ScriptEngineMinorVersion() < 8) {
    //这是 IE8-
}

实际上,IE 支持的条件编译功能中,有个表示 JScript 版本的条件编译变量,如下(完整的条件编译变量清单见这里.aspx)):

<script type="text/javascript">
    /*@cc_on
        alert(@_jscript_version);
    @*/
</script>

这个变量是「major.minor」格式的 JScript 版本号,跟使用 JS 接口获取到的版本号一致,也只取决于浏览器版本,不受浏览器模式和文本模式的影响。

文本模式对 JScript 没影响?

看完上一节,再看我之前写的这段:文本模式决定:1)排版引擎;2)JS引擎,有明显的矛盾。难道之前的结论有误?

实际上,JS 获取到的 JScript 版本号仅仅表示了当前浏览器自带的 JScript 引擎版本(例如 IE9 始终是 9.0,IE7 始终是 5.7),并不代表任何情况都可以使用这个版本 JS 引擎的所有功能,页面使用哪种版本的 JScript 引擎还是由页面的文本模式来决定

例如,IE8 的 JScript 版本是 5.8,但是只有在文本模式等于 IE8标准 时才可以使用 JScript5.8 的功能,其它任何文本模式都会导致页面使用 JScript5.7。

举个例子,JScript5.8 增加了对 JSON 的支持,所以 IE8 支持原生 JSON 对象。但网上很多人问为什么在 IE8/9 下无法使用原生 JSON。一种可能是页面没写 DTD,导致页面进入了 IE5怪异 这种文本模式,进而启用了不支持原生 JSON 的 JScript5.7 导致的。

再举几个例子:[,].length 在 JScript9.0 之前是 2;[1,2,3].join(undefined) 在 JScript5.8 之前是"1undefined2undefined3"。把 IE9 的文本模式分别改成 IE9标准IE8标准 IE7标准,可以得到下表:

文本模式 [,].length[1,2,3].join(undefined)使用的 JScript 版本
IE7标准 2 "1undefined2undefined3" 5.7
IE8标准 2 "1,2,3" 5.8
IE9标准 1 "1,2,3" 9.0

类似的例子还有很多。大部分情况下,页面使用的 JScript 引擎版本会随着文本模式的降低而退化,页面对 JS 的支持度也随之退化。但教主 franky 提供了一个「for in顺序」的反例:

var o = {1 : '0', 0 : '1'}; for(var i in o) { console.log(i); }

对于 IE9+ 浏览器,这行代码输出顺序始终是 0 1;对于 IE9 以下的浏览器,输出顺序都是 1 0,并不受文本模式的影响。关于这一点我没想到比较好的解释。

一些 DOM 相关的方法,如 document.querySelectorAll,在文本模式为 IE7标准IE5怪异 时不可用;addEventListener 在文本模式为 IE8标准IE7标准IE5怪异 时不可用。这说明 IE 浏览器对 DOM 的支持度也会随着文本模式的降低而退化。

但是一些 BOM 方法,却跟文本模式无关。例如 IE8 开始支持的 postMessage 和 localStorage,只要浏览器是 IE8+,无论什么文本模式,这两个功能都可用。IE9+ 支持的 window.performance,在 IE9+ 浏览器上,也始终可用,跟当前的文本模式无关。

小结下:随着文本模式的降低,页面上实际使用的 JScript 引擎会退化,一些高版本支持的语言特性不再可用(如 JSON),但 for in 的顺序问题似乎不会退化;DOM 相关功能也有相应的退化;但大部分 BOM 接口却不会退化(如 localStorage)。

总结

本文讨论的内容在各部分都小结过,最后只说一个结论:在解决 JS 兼容性问题时,一定要使用能力检测和特性检测。因为无论是从 UA 中得到的浏览器信息,还是从 JS 接口中获取到的 JScript 引擎版本,都非常不可靠。例如 UA 中包含 IE7,并不一定不支持 IE9+ 的 window.performace。也可能是 IE9 浏览器使用了 IE9兼容性视图,UA 确实会变成 IE7,文本模式为 IE7标准,但不影响对 window.performace 的支持。

另外,虽然 IE 的浏览器模式和文本模式非常复杂,组合起来有几十种情况,但大部分情况只能通过开发者工具来构造。例如 UA 中包含 IE9,实际上使用 JScript5.7 的情况(浏览器模式为 IE9,文本模式为 IE7标准),正常情况下不会出现。

参考:

本文链接:参与评论 »

--EOF--

提醒:本文最后更新于 1188 天前,文中所描述的信息可能已发生改变,请谨慎使用。

专题「浏览器」的其他文章 »

Comments