使用 ES6 的浏览器兼容性问题

之前对浏览器兼容性问题只是大概知道一些点,没想到此次真正着手去作的时候,仍是碰到了不少问题。刚开始的时候一边解决问题,一边想着:用 IE8 的都是神经病,到后来,我发现完了,I LOVE IE。javascript

0x00 起源

在此次作小蜜 PC 版的时候,因为早于 PC 版,无线版已经从新设计了全新版,作了不少架构上的优化调整。因此在作的时候把无线版的前端架构拿了过来,主要的考虑就是品牌和功能保持跟无线版统一的同时,技术上也可相互支持以及组件复用。php

无线版技术上主要采用 ES6 + Webpack + Babel 的方式,因为项目的独特性和特殊需求,并无使用任何框架,只引入 zepto 做为一个标准支撑库。html

而 PC 版的架构跟无线版基本保持一致,主要是把 zepto 换成了 jQuery。前端

下面是一些基本的开发依赖:java

{
  "devDependencies": {
    "babel-core": "~6.3.15",
    "babel-loader": "~6.2.0",
    "babel-preset-es2015": "~6.3.13",
    "babel-preset-stage-0": "~6.3.13",
    "babel-runtime": "~6.3.13",
    "extract-text-webpack-plugin": "~0.9.1",
    "less-loader": "~2.2.1",
    "nunjucks-loader": "~1.0.7",
    "style-loader": "~0.10.2",
    "webpack": "~1.12.9",
    "webpack-dev-server": "^1.10.1"
  }
}

0x01 polyfill

因为 Babel 默认只转换转各类 ES2015 语法,而不转换新的 API,好比 Promise,以及 Object.assign、Array.from 这些新方法,这时咱们须要提供一些 ployfill 来模拟出这样一个提供原生支持功能的浏览器环境。webpack

主要有两种方式:babel-runtimebabel-polyfillgit

babel-runtime

babel-runtime 的做用是模拟 ES2015 环境,包含各类分散的 polyfill 模块,咱们能够在本身的模块里单独引入,好比 promise:github

import 'babel-runtime/core-js/promise'

它们不会在全局环境添加未实现的方法,只是这样手动引用每一个 polyfill 会很是低效,咱们能够借助 Runtime transform 插件来自动化处理这一切。web

首先使用 npm 安装:chrome

npm install babel-plugin-transform-runtime --save-dev

而后在 webpack 配置文件的 babel-loader 增长选项:

loader: ["babel-loader"],
query: {
  plugins: [
    "transform-runtime"
  ],
  presets: ['es2015', 'stage-0']
}

babel-polyfill

babel-polyfill 是针对全局环境的,引入它浏览器就好像具有了规范里定义的完整的特性,一旦引入,就会跑一个 babel-polyfill 实例。用法以下:

1.安装 babel-polyfill

npm install babel-polyfill --save

2.在入口文件中引用:

import 'babel-polyfill'

小结

其实作到这些,在大部分浏览器就能够正常跑了,但咱们作的是一个用户环境很不肯定的产品,对一些年代久远但又不容忽视的运行环境,好比 IE8,咱们作的还不够。

接下来将开始讲述咱们在兼容性方面遇到的一些问题,和解决方法。

0x02 开始在 IE8 运行

最开始作的时候并无针对 IE 作一些兼容性方面的处理,结果在 IE8 上一跑一堆问题。

第一步,咱们把 jQuery 换成 1.12.1 ,由于 2.X 已经再也不支持 IE8。

但并无像咱们想象中的那样,只是简单换一下 jQuery 版本就能够正常运行了。

0x03 default or catch

这是遇到的第一个问题。在兼容性测试过程当中,对下面的代码:

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

或者这种:

module.exports = _main2.default;

在 IE8 下会直接报”缺乏标识符、字符串或数字”的错。

咱们得在对象的属性上加 '' 才能够。就像下面这样:

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { 'default': obj };
}

module.exports = _main2['default'];

至于缘由,并非 IE8 下对象的属性必须得加 '' 才行,而是 default 的问题,做为一个关键字,一样的问题还包括 catch

这两种状况,能够经过使用 transform-es3-property-literalstransform-es3-member-expression-literals 这两个插件搞定。

总之,在平时写代码的时候避免使用关键字,或者保留字做为对象的属性值,尤为是在习惯不加引号的状况下。相关讨论:Allow reserved words for properties

0x04 es5-shim、es5-sham

为了兼容像 IE8 这样的老版本浏览器,咱们引入 es5-shim 做为 polyfill。

但在遇到 Object.defineProperty 仍提示 "对象不支持此操做"

As currently implemented, the Object.defineProperty shim will not install on IE8 because IE8 already has such a method. However, the built-in IE8 method only works when applied to DOM objects.

其实 es5-shim 明确说明,这个方法的 polyfill 在 IE8 会失败,由于 IE8 已经有个同名的方法,但只是用于 DOM 对象。

一样的问题还包括 Object.create,上述问题能够再引入 es5-sham 解决.

0x05 addEventListener

项目中有部分代码直接使用 addEventListener 这个 API,但在 IE8 下的事件绑定并非这个方法。

这个问题很容易解决,也无需去写额外的 polyfill。咱们已经把 jQuery 换成 1.x,因此只需把代码中 addEventListener 换成 jQuery 的写法就 Okay 了。

jQuery 其实为咱们封装了不少 API,并作了不少兼容性的封装,相似的只要使用封装好的就能够了。

0x06 没法获取未定义或 null 引用的属性

这个问题是在特定场景下【转人工】出现的,出现问题的不是 IE8,而是 IE9 和 IE10。

缘由是 ocs 实例建立失败,由于没有调用父类的构造函数。

经过安装 transform-es2015-classestransform-proto-to-assign 解决。

在配置项加上这两个插件的配置:

{
  "plugins": [
      ["transform-es2015-classes", { "loose": true }],
      "transform-proto-to-assign"

  ]
}

0x07 postMessage

虽然 postMessage 是 HTML5 的特性,但 IE8 和 Firefox3 很早就实现了这个 API,固然,跟后来的标准并不一致。这其实也不能怪 IE8。

The postMessage method is supported in Internet Explorer from version 8, Firefox from version 3 and Opera from version 9.5.

咱们可能会这样去使用:

parent.postMessage({success: 'ok', name: ‘mirreal’}, ‘*’);

可是为了兼容 IE8,咱们得转成字符串:

parent.postMessage(JSON.stringify({success: 'ok', name: "mirreal"}), ‘*’);

另一个须要注意的点是:在 IE8 下 window.postMessage 是同步的。

window.postMessage is syncronouse in IE 8

var syncronouse = true;
window.onmessage = function () {
  console.log(syncronouse); // 在 IE8 下会在控制台打印 true
};
window.postMessage('test', '*');
syncronouse = false;

0x08 IE8/IE9 的控制台

遇到一个奇怪的问题,在刚开始遇到的时候(其实搞清楚缘由,好像也挺正常的),小蜜在 IE8 IE9 没法加载。在 IE8 那个古老浏览器的左下角,好像也是惟一会在页面提示脚本错误的浏览器,提示 script error

第一反应就是应该又是某个函数在 IE 下不支持,准备打开控制台看看到底哪里报错,结果却什么事都没有了,页面居然顺畅地加载出来了,这下该怎么调试好呢?

开始思考:什么东西是依赖控制台而存在的,到底会是什么呢。。。其实就是控制台自己。

缘由就是咱们在代码中添加了一些控制信息会打印在控制台,而 IE8/IE9 要开启 IE Dev Tools 才能使用 console 对象。

切忌把 IE8/9 想成 Chrome/Firefox,觉得永远有 window.console 可用.终于,IE10 改邪归正,console 再也不像段誉的六脉神剑时有时无。

console.log is there in IE8, but the console object isn't created until you open DevTools. Therefore, a call to console.log may result in an error, for example if it occurs on page load before you have a chance to open the dev tools.

但只要 IE8/9 还在一天,console 检查仍是不能少的

事实上,IE8/9 从未死去,因此

就像这样:

if (window.console) {
  console.log('log here');
}

要是有一堆 console.log, console.count, console.error, console.time, console.profile,... 这样去写,那还不把人写到恶心死。

写个简单的 console polyfill 吧,检测是否存在 console,不存在能够常见一个同名的空方法达到不报错的目的。固然,生产环境的代码其实也不会有那么多奇奇怪怪的 console

0x09 定义文档兼容性

X-UA-Compatible 当初是针对 IE8 新加的一个配置。用于为 IE8 指定不一样的页面渲染模式,好比使用 IE7 兼容模式,或者是采用最新的引擎。

如今基本也不须要前者的降级模式,更多的是写入 IE=edge 支持最新特性。而 chrome=1 则会激活 Google Chrome Frame,前提是你的 IE 安装过这个插件。

有什么用呢,固然有用,有些 API 是做为新特性存在于 IE8 中的,好比 JSON,不开启的话就用不了。

为何要用 X-UA-Compatible?

在 IE8 刚推出的时候,不少网页因为重构的问题,没法适应较高级的浏览器,因此使用 X-UA-Compatible 强制 IE8 采用低版本方式渲染。

好比:使用下面这段代码后,开发者无需考虑网页是否兼容 IE8 浏览器,只要确保网页在 IE六、IE7 下的表现就能够了。

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />

而这段代码:

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

IE=edge 告诉 IE 使用最新的引擎渲染网页,chrome=1 则能够激活 Chrome Frame[1]。

0x0a 条件注释 or 条件编译

最后说说 IE 的条件注释,用法以下:

!    [if !IE]    The NOT operator. This is placed immediately in front of the feature, operator, or subexpression to reverse the Boolean meaning of the expression.

lt    [if lt IE 5.5]    The less-than operator. Returns true if the first argument is less than the second argument.

lte    [if lte IE 6]    The less-than or equal operator. Returns true if the first argument is less than or equal to the second argument.

gt    [if gt IE 5]    The greater-than operator. Returns true if the first argument is greater than the second argument.

gte    [if gte IE 7]    The greater-than or equal operator. Returns true if the first argument is greater than or equal to the second argument.

( )    [if !(IE 7)]    Subexpression operators. Used in conjunction with boolean operators to create more complex expressions.

&    [if (gt IE 5)&(lt IE 7)]    The AND operator. Returns true if all subexpressions evaluate to true

|    [if (IE 6)|(IE 7)]    The OR operator. Returns true if any of the subexpressions evaluates to true.

另一个相似的东西是在 Javascript 中的条件编译(conditional compilation)。咱们可使用这段简单的代码来作浏览器嗅探:

var isIE = /*@cc_on!@*/false

在其余浏览器中,false 前的被视为注释,而在 IE 中,/*@cc_on .... @*/ 之间的部分能够被 IE 识别并做为程序执行,同时启用 IE 的条件编译。

经常使用变量以下:

* @_win32 若是在 Win32 系统上运行,则为 true。
* @_win16 若是在 Win16 系统上运行,则为 true。
* @_mac 若是在 Apple Macintosh 系统上运行,则为 true。
* @_alpha 若是在 DEC Alpha 处理器上运行,则为 true。
* @_x86 若是在 Intel 处理器上运行,则为 true。
* @_mc680x0 若是在 Motorola 680x0 处理器上运行,则为 true。
* @_PowerPC 若是在 Motorola PowerPC 处理器上运行,则为 true。
* @_jscript 始终为 true。
* @_jscript_build 包含 JavaScript 脚本引擎的生成号。
* @_jscript_version 包含 major.minor 格式的 JavaScript 版本号。

Internet Explorer 11 以前的全部版本的 Internet Explorer 都支持条件编译。 从 Internet Explorer 11 标准模式开始,Windows 8.x 应用商店应用不支持条件编译。

以前一直在作移动端的开发,没想到作 PC 端也会遇到这么多的兼容性问题。不一样于移动端设备的繁杂和不肯定性,PC 版的兼容更侧重于对特定浏览器的特性的了解,相比而言更为明确,而非由于某一款手机的诡异表现。

参考文档

相关文章
相关标签/搜索