ES6 的浏览器兼容性问题

  |  
 阅读次数

eg: ES6 + Webpack + Babel

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"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 来模拟出这样一个提供原生支持功能的浏览器环境。

主要有两种方式:babel-runtime 和 babel-polyfill。

babel-runtime

babel-runtime 的作用是模拟 ES2015 环境,包含各种分散的 polyfill 模块,我们可以在自己的模块里单独引入,比如 promise:

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

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

首先使用 npm 安装:

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

然后在 webpack 配置文件的 babel-loader 增加选项:

1
2
3
4
5
6
7
loader: ["babel-loader"],
query: {
plugins: [
"transform-runtime"
],
presets: ['es2015', 'stage-0']
}

babel-polyfill

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

1.安装 babel-polyfill

1
npm install babel-polyfill --save

2.在入口文件中引用:

1
import 'babel-polyfill'

小结

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

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

0x02 开始在 IE8 运行

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

第一步,我们把 jQuery 换成 1.12.1 ,因为 2.X 已经不再支持 IE8。

但并没有像我们想象中的那样,只是简单换一下 jQuery 版本就可以正常运行了。

0x03 default or catch

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

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

或者这种:

1
module.exports = _main2.default;

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

我们得在对象的属性上加 ‘’ 才可以。就像下面这样:

1
2
3
4
5
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { 'default': obj };
}

module.exports = _main2['default'];

至于原因,并不是 IE8 下对象的属性必须得加 ‘’ 才行,而是 default 的问题,作为一个关键字,同样的问题还包括 catch。

这两种情况,可以通过使用 transform-es3-property-literals 和 transform-es3-member-expression-literals 这两个插件搞定。

0x04 es5-shim、es5-sham

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

但在遇到 Object.defineProperty 仍提示 “对象不支持此操作”

1
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-classes 和 transform-proto-to-assign 解决。

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

1
2
3
4
5
6
7
{
"plugins": [
["transform-es2015-classes", { "loose": true }],
"transform-proto-to-assign"

]
}

0x07 postMessage

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

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

我们可能会这样去使用:

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

但是为了兼容 IE8,我们得转成字符串:

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

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

1
window.postMessage is syncronouse in IE 8
1
2
3
4
5
6
var syncronouse = true;
window.onmessage = function () {
console.log(syncronouse); // 在 IE8 下会在控制台打印 true
};
window.postMessage('test', '*');
syncronouse = false;

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 浏览器,只要确保网页在 IE6、IE7 下的表现就可以了。

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

而这段代码:

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

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