数往知来:一次浏览器兼容工作中的知识点分析

在这个机器学习和人工智能遍地的年代,前端开发中的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,分别用以下措施应对:

  1. 取消表单中上传头像的本地预览功能

  2. 有上传头像的表单从ajax提交改为原生提交,并在后台接口兼容

  3. 取消富文本编辑器中的上传图片功能(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,并反映在了由其重写过的historylocation两个对象中。

需要注意的是,historylocation两个对象是从组件的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://www.impressivewebs.com/ie10-css-hacks/

  • 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 关注我们哦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值