在这个机器学习和人工智能遍地的年代,前端开发中的PC端浏览器兼容问题显得已经不是那么时髦和迫切了;刨去某些面向传统行业或网银支付等领域还不得不面对这个具体的问题外,大部分网站和移动端应用似乎可以潇洒的回避了;兼容工作的重点已经从几年前的样式统一转变为在PC端和移动端对新特性的支持和妥协,除了能更好更全面的满足用户,开发者了解优雅降级的兼容化思路,也是可以普遍应用在各项工作中的
开车!
开车!
开车!
本次用来分析的项目,其package.json中的依赖大致如下:
"dependencies": {
"bootstrap": "^3.3.7",
"draft-js": "^0.10.1",
"draftjs-to-html": "^0.7.4",
"element-dataset": "^2.2.6",
"form-serialize": "^0.7.1",
"html-to-draftjs": "0.1.0-beta14",
"immutable": "~3.7.4",
"lodash": "^4.17.4",
"mobx": "^3.1.9",
"mobx-react": "^4.1.5",
"moment": "^2.18.1",
"react": "^15.6.1",
"react-bootstrap": "^0.30.8",
"react-datetime": "^2.8.9",
"react-dom": "^15.6.1",
"react-draft-wysiwyg": "^1.10.7",
"react-router-dom": "^4.1.0",
"native-promise-only": "^0.8.1",
"whatwg-fetch": "^2.0.3"
}
显然,这是一个bootstrap样式的后台单页应用,用react实现了组件化、用mobx管理状态、引入了fetch等promise异步工具,并且使用了一些日期选择和富文本编辑器插件等第三方库
--- 感觉上IE就悬乎乎哒ㄟ( ▔, ▔ )ㄏ
该产品为 toB 形态,主要面对部分可控的目标用户,大部分可以在指导下使用较新的chrome浏览器,但不排除一些用户使用firefox甚至IE的情况,所以针对该项目的主要目标就是让低版本IE用户处于“大部分特性可用、鼓励升级到chrome”的状况下,而不是回避甚至放弃这部分需求
这里我们以兼容后的
index.html
入口文件为切入点,梳理本次兼容过程的脉络:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9,edge">
<title><%= htmlWebpackPlugin.options.title %></title>
<!--[if lte IE 9]>
<script src="CDN/polyfill.min.js"></script>
<script src="CDN/es5-shim.min.js"></script>
<script src="CDN/es5-sham.min.js"></script>
<script src="CDN/es6-shim.min.js"></script>
<script src="CDN/es6-sham.min.js"></script>
<script src="CDN/json3.min.js"></script>
<script src="CDN/classList.min.js"></script>
<script src="CDN/selectivizr-min.js"></script>
<script src="CDN/native.history.js"></script>
<script>
window.__defineGetter__(
'history',
function(){
return History
}
);
</script>
<![endif]-->
<script>
(function() {
var _isIE = /Trident\/(\d+)/i.exec(navigator.userAgent);
var _gteIE10 = _isIE && parseInt(_isIE[1])>5;
if (_gteIE10) {
var s = document.createElement('script');
document.head.appendChild(s);
s.src = "CDN/es6-shim.min.js";
};
}())
</script>
</head>
<body>
<!--[if lte IE 9]>
<div id="oldIENoticeBox">
您的浏览器过于老旧,
请使用<a href="...">最新版chrome浏览器</a>
</div>
<![endif]-->
<div id="root"></div>
</body>
</html>
X-UA-Compatible
“有时候需要限制Windows Internet Explorer在解析某个网页时使用特定的文档模式。使用
X-UA-Compatible
头部属性,可以让用户就像使用旧版本IE一样查看当前网页” -- MSDN
X-UA-Compatible
设置的被称为遗留文档模式(legacy document modes)!doctype
的情况,从而有更好的兼容性content="IE=7,9,10"
,IE将从中选中自身能支持的最高版本chrome=1
,则表示支持Google Chrome Frame
外挂插件(在IE外观下调用chrome内核浏览的挖墙脚插件;相应的也有个IETab用来在chrome/firefox下调用IE页面?)X-UA-Compatible
设置遗留文档模式后,会带来新的问题,那就是 navigator.userAgent 返回的 MSIE 版本都是被模拟的值,而真实的浏览器版本难以判断了`Trident/7.0` IE11 `Trident/6.0` IE10 `Trident/5.0` IE9 `Trident/4.0` IE8
“条件注释 (conditional comment) 是于HTML源码中被 Microsoft Internet Explorer 有条件解释的语句。条件注释可被用来向 Internet Explorer 提供及隐藏代码” -- wiki
IE中有两种特有的条件注释:HTML条件注释 和 JScript条件注释
语法为
<!--[if expression]> HTML <![endif]-->
举例:<!--[if IE 5]><p>欢迎来到IE5!</p><![endif]--><!--[if IE 5.0002]><p>欢迎来到Win2000中的IE5!</p><![endif]--><!--[if lt IE 5.5]><p>小于IE5.5</p><![endif]--><!--[if lte IE 6]><p>小于等于IE6</p><![endif]--><!--[if gt IE 6]><p>大于IE6</p><![endif]--><!--[if gte IE 6]><p>大于等于IE6</p><![endif]--><!--[if !(IE 7)]><p>不等于IE7</p><![endif]--><!--[if (gt IE 5)&(lt IE 8)]><p>大于IE5且小于IE8</p><![endif]--><!--[if (IE 6)|(IE 7)]><p>IE6或IE7</p><![endif]-->
如下是一个“下层显示”条件“注释”的示例,它除了误导向的名字之外,根本不是一个 (X)HTML 注释,使用默认的微软语法:
<![if !IE]>
<link href="non-ie.css" rel="stylesheet">
<![endif]>
微软承认这种句法不是标准化的标记,其意图是这些标记被其它浏览器忽视并暴露其中的内容
关于JScript:
自 Internet Explorer 4 开始,存在一种于 JScript 之中加入条件注释的类似的专有的机理,名称是条件编译:
<script>
/*@cc_on
document.write("You are using IE4 or higher");
@*/
</script>
预变量 @_jscript_version
:
<script>
/*@cc_on @if (@_jscript_version == 10)
document.write("You are using IE10"); @elif (@_jscript_version == 9)
document.write("You are using IE9");
@elif (@_jscript_version == 5.8)
document.write("You are using IE8");
@elif (@_jscript_version == 5.7 && window.XMLHttpRequest)
document.write("You are using IE7"); @elif (@_jscript_version == 5.6
|| (@_jscript_version == 5.7 && !window.XMLHttpRequest))
document.write("You are using IE6"); @elif (@_jscript_version == 5.5)
document.write("You are using IE5.5"); @else
document.write("You are using IE5 or older"); @end@*/
</script>
<!--[if !IE]><!--><script>
if (/*@cc_on!@*/false) {
document.documentElement.className+=' ie10';
}
</script><!--<![endif]-->
这3个古怪的单词一般都用来描述一些给浏览器打补丁的第三方库
简单的说,他们的作用和区别是:
shim
是一个库,它将一个新的API引入到一个旧的环境中,而且仅靠旧环境中已有的手段实现。有时候也称为shiv
shim
也无法被完美模拟的方法,就由sham
尽量去模拟。sham
只承诺你用的时候代码不会崩溃polyfill
就是一段代码(或者插件),提供了那些开发者们希望浏览器原生提供支持的功能。因此,一个polyfill
就是一个用在浏览器API上的特殊shim
发端于20世纪30年代非裔美国人社区的一种踢踏舞。流传度非常高,以至于有人说几乎所有踢踏舞(tap)和摇摆舞(swing)舞者都会跳,是“踢踏舞/摇摆舞中的国际歌”
另一个非常有感染力的版本:http://v.youku.com/v_show/id_XMTU5ODgyMTY2NA.html
一个中文教学视频:http://my.tv.sohu.com/us/275736703/82663464.shtml
英国有一种品牌为Polyfilla的墙面填料,这种填料在美国叫Spackling Paste(Spackle是美国抹墙粉的一个品牌)-- 也就是我们一般叫做“腻子”或“填泥”的东西(对应的英文单词是putty和filler)
polyfill
的作者正是英国人,他把浏览器想象成有裂缝的墙面,而用腻子可以把这些裂缝填平,最后得到的是光滑的浏览器“墙面”
万能的某宝:
类似的常用单词还有用来表示变量中“张三李四”的foo bar
等,其解释可见 http://blog.csdn.net/deargua/article/details/1633123
es5-shim
es5-sham
es6-shim
es6-sham
json3
history.js
本次难以兼容的正是HTML5 File API
,简单的说就是:IE10及以下不支持FileReader
,分别用以下措施应对:
本项目中的路由是由react-router
中的<BrowserRouter>
负责的,其官网的介绍如下:
A that uses the HTML5 history API (pushState, replaceState and the popstate event) to keep your UI in sync with the URL
言简意赅,react-router
中的页面跳转,其实就是封装了HTML5 history API
,并反映在了由其重写过的history
和location
两个对象中。
需要注意的是,history
和location
两个对象是从组件的props
中获得的 -- 并非window
中默认的全局对象。
简单的说,手动实现跳转的流程就是:
history.push(path, [state])
或history.replace(path, [state])
等实现url变化并传递参数location.state
得到传递的参数实际对应的HTML5 history API
方法则是:
history.pushState()
或history.replaceState()
该项目中,引入了 https://github.com/browserstate/history.js/ 并做相关处理覆盖了window.history,从而实现了基本兼容IE9/10
至于零零碎碎的 IE css hack ,或 classList 等,就不展开细说了;通过以上总结和梳理,发现了很多我们已经习以为常的用法背后的原理,以及一些技术的发展脉络,相信在以后的应用中,会对相关技术更加心中有数,也能在其他工作中,更合理的分析和取舍