在这个机器学习和人工智能遍地的年代,前端开发中的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”的状况下,而不是回避甚至放弃这部分需求
兼容原则
尽量不影响chrome等其他主流的浏览器
最大化的尝试兼容已有功能
对实在无法实现的功能降级处理
对IE向下兼容到9(xp下可升级的最高版本)
顺藤摸瓜
这里我们以兼容后的
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)X-UA-Compatible不区分大小写,但必须出现在head中,且必须位于除title及其他meta元素外的元素前面
服务器也可以通过配置指定X-UA-Compatible,但网页中的优先级高于服务器发送的
可以设置其content值为诸如 IE9 或 EmulateIE9 之类的值;前者严格限制按照指定的版本渲染,而后者还会考虑
!doctype
的情况,从而有更好的兼容性设置content为edge则将Internet Explorer置于其支持的最高级模式之下
可以设置多个值,比如
content="IE=7,9,10"
,IE将从中选中自身能支持的最高版本如果content值中包含
chrome=1
,则表示支持Google Chrome Frame
外挂插件(在IE外观下调用chrome内核浏览的挖墙脚插件;相应的也有个IETab用来在chrome/firefox下调用IE页面😆)
判断真实的IE版本
使用
X-UA-Compatible
设置遗留文档模式后,会带来新的问题,那就是 navigator.userAgent 返回的 MSIE 版本都是被模拟的值,而真实的浏览器版本难以判断了对于IE8以上,userAgent中包含了Trident内核的版本,可以用来判断真实版本
对应关系为
`Trident/7.0` IE11 `Trident/6.0` IE10 `Trident/5.0` IE9 `Trident/4.0` IE8
IE的条件注释
“条件注释 (conditional comment) 是于HTML源码中被 Microsoft Internet Explorer 有条件解释的语句。条件注释可被用来向 Internet Explorer 提供及隐藏代码” — wiki
IE中有两种特有的条件注释:HTML条件注释 和 JScript条件注释
HTML条件注释
语法为
<!--[if expression]> HTML <![endif]-->
条件注释最初于微软的 Internet Explorer 5浏览器中出现,直至 IE10 停止支持
对于非IE浏览器,被当作普通注释而忽略
举例: <!--[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]-->
下层显示(downlevel-revealed)的HTML条件注释
如下是一个“下层显示”条件“注释”的示例,它除了误导向的名字之外,根本不是一个 (X)HTML 注释,使用默认的微软语法:
<![if !IE]> <link href="non-ie.css" rel="stylesheet"> <![endif]>
微软承认这种句法不是标准化的标记,其意图是这些标记被其它浏览器忽视并暴露其中的内容
JScript条件注释
关于JScript:
JScript是由微软公司开发的活动脚本语言,是微软对ECMAScript规范的实现。JScript最初是随IE3.0于1996年8月发布
IE 6-7 支持的 JScript5,以及IE8支持的JScript6,大致相当于 ECMAScript 3 / JavaScript 1.5
JScript最新的版本是基于尚未定稿的ECMAScript4.0版规范的JScript .NET,并且可以在微软的.Net环境下编译。JScript在ECMA的规范上增加了许多特性
JScript、JavaScript,以及Flash开发中的ActionScript等,都是ECMA的实现,可以认为是几种方言
自 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>
IE11 Standards mode 和 Windows 8.x Store apps 中不支持
IE10及更早版本的Standards mode中都支持
结合两种注释的识别IE10奇技淫巧
<!--[if !IE]><!--><script> if (/*@cc_on!@*/false) { document.documentElement.className+=' ie10'; } </script><!--<![endif]-->
姥姥不疼:IE6-9发现了HTML条件注释但返回了false
舅舅不爱:IE11两种注释都不认
IE10同时满足两种注释的交集
shim / sham / polyfill
这3个古怪的单词一般都用来描述一些给浏览器打补丁的第三方库
简单的说,他们的作用和区别是:
一个
shim
是一个库,它将一个新的API引入到一个旧的环境中,而且仅靠旧环境中已有的手段实现。有时候也称为shiv
由
shim
也无法被完美模拟的方法,就由sham
尽量去模拟。sham
只承诺你用的时候代码不会崩溃一个
polyfill
就是一段代码(或者插件),提供了那些开发者们希望浏览器原生提供支持的功能。因此,一个polyfill
就是一个用在浏览器API上的特殊shim
词源考:shim sham
发端于20世纪30年代非裔美国人社区的一种踢踏舞。流传度非常高,以至于有人说几乎所有踢踏舞(tap)和摇摆舞(swing)舞者都会跳,是“踢踏舞/摇摆舞中的国际歌”
另一个非常有感染力的版本:http://v.youku.com/v_show/id_XMTU5ODgyMTY2NA.html
一个中文教学视频:http://my.tv.sohu.com/us/275736703/82663464.shtml
词源考:polyfill
英国有一种品牌为Polyfilla的墙面填料,这种填料在美国叫Spackling Paste(Spackle是美国抹墙粉的一个品牌)– 也就是我们一般叫做“腻子”或“填泥”的东西(对应的英文单词是putty和filler)
polyfill
的作者正是英国人,他 把浏览器想象成有裂缝的墙面,而用腻子可以把这些裂缝填平,最后得到的是光滑的浏览器“墙面”
万能的某宝:
类似的常用单词还有用来表示变量中“张三李四”的foo bar
等,其解释可见 http://blog.csdn.net/deargua/article/details/1633123
几个典型的补丁
es5-shim
Array.prototype.filter
Function.prototype.bind
Number.prototype.toPrecision
es5-sham
Object.create
es6-shim
String.prototype.startsWith
Array.from
Array.prototype.find
es6-sham
Function.prototype.name
json3
JSON.stringify
JSON.parse
history.js
History.pushState
History.replaceState
File API
本次难以兼容的正是HTML5 File API
,简单的说就是:IE10及以下不支持 FileReader
,分别用以下措施应对:
取消表单中上传头像的本地预览功能
有上传头像的表单从ajax提交改为原生提交,并在后台接口兼容
取消富文本编辑器中的上传图片功能(PRD中没有特别提及,仅在UI图上出现,优先级不高)
History API
本项目中的路由是由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()
window.addEventListener(“popstate”, e=>e.state)
该项目中,引入了 https://github.com/browserstate/history.js/ 并做相关处理覆盖了window.history,从而实现了基本兼容IE9/10
总结
至于零零碎碎的 IE css hack ,或 classList 等,就不展开细说了;通过以上总结和梳理,发现了很多我们已经习以为常的用法背后的原理,以及一些技术的发展脉络,相信在以后的应用中,会对相关技术更加心中有数,也能在其他工作中,更合理的分析和取舍
参考资料
https://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx
http://zcfy.cc/article/specifying-legacy-document-modes-internet-explorer-95.html
http://blog.csdn.net/z69183787/article/details/17437195
https://msdn.microsoft.com/en-us/library/ms537512(v=vs.85).aspx
https://zh.wikipedia.org/wiki/%E6%9D%A1%E4%BB%B6%E6%B3%A8%E9%87%8A
https://stackoverflow.com/questions/135203/whats-the-difference-between-javascript-and-jscript
https://docs.microsoft.com/en-us/scripting/javascript/reference/at-cc-on-statement-javascript
https://en.wikipedia.org/wiki/Trident%28layoutengine%29
http://www.easy-swing.be/en/catalog/7-swing-history/59-history-of-shim-sham/
https://www.douban.com/event/24601886/
http://www.aichengxu.com/other/3730486.htm
https://stackoverflow.com/questions/6599815/what-is-the-difference-between-a-shim-and-a-polyfill
https://www.v2ex.com/t/250434
http://blog.csdn.net/bugknightyyp/article/details/8840111
http://www.jitterbuzz.com/less7.html
https://code.tutsplus.com/tutorials/an-introduction-to-the-html5-history-api–cms-22160
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/GlobalObjects/Object/defineGetter_
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
————————————-
长按二维码或搜索 fewelife 关注我们哦