## 低版本浏览器兼容（IE9- IE11）

​	当用 ES6及更新的语法来写 JavaScript时，如果需要兼容老版本的浏览器（不支持 ES6），需要用户单独进行兼容。

​	主流的兼容方式有以下几种：

+ 通过Babel 转码
+ 通过[Polyfill.io API](https://polyfill.io/)自动加载（服务端检测特性支持）
+ dynamic polyfill（本地检测特性支持）



### 通过Babel转码

​	通过Babel转码是标准的做法，其流程为：

```
编写ES6 代码 → 编译成 ES5 （通过 Babel等）→ 浏览器执行
```

​	在引入了Babel的工程中，可以通过以下几种方式来引入兼容代码：

+ 手动按需引入
+ 入口批量引入
+ 手动按浏览器版本引入
+ 通过Babel Presets自动引入



#### 手动按需引入

​	这种方式是指需要什么就在对应的也没进行单独引入，如：

```js
// components/button.jsx
import 'core-js/es6/object'；
...
```

​	这种方式精确，但比较难维护。



#### 入口批量引入

​	这种方式主要是通过在项目的入口文件`src/index.js`中直接引入对应的polyfill库。	

```
cnpm install babel-polyfill --save   //Babel 6
or
cnpm install @babel/polyfill --save   //Babel 7
```
```js
// src/index.js
import 'babel-polyfill';   //Babel 6
or
import "@babel/polyfill";  //Babel 7
```

​	这种方式比较暴力，直接加载整个polyfill库，代码体积会大幅增加。推荐的方式把需要兼容的特性放到polyfill.js中统一管理：

```js
// src/index.js
import './polyfill';

// src/polyfill.js
/*
 * required polyfills
 * url: https://reactjs.org/docs/javascript-environment-requirements.html
 */

// 单独引入
/** IE9, IE10 and IE11 requires all of the following polyfills. * */
// import 'core-js/es6/symbol'
// import 'core-js/es6/object'
// import 'core-js/es6/function'
// import 'core-js/es6/parse-int'
// import 'core-js/es6/parse-float'
// import 'core-js/es6/number'
// import 'core-js/es6/math'
// import 'core-js/es6/string'
// import 'core-js/es6/date'
import 'core-js/es6/array';
// import 'core-js/es6/regexp'
import 'core-js/es6/map';
// import 'core-js/es6/weak-map'
import 'core-js/es6/set';
import 'core-js/es7/object';
// require('raf').polyfill(window);
/** IE10 and IE11 requires the following for the Reflect API. */
import 'core-js/es6/reflect';

/** Evergreen browsers require these. * */
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';

// CustomEvent() constructor functionality in IE9, IE10, IE11
(function() {
    if (typeof window.CustomEvent === 'function') return false;

    function CustomEvent(event, params) {
        let tParams = params || { bubbles: false, cancelable: false, detail: undefined };
        const evt = document.createEvent('CustomEvent');
        evt.initCustomEvent(event, tParams.bubbles, tParams.cancelable, tParams.detail);
        return evt;
    }

    CustomEvent.prototype = window.Event.prototype;

    window.CustomEvent = CustomEvent;
})();

```



#### 手动按浏览器版本引入

CRA2开始默认不支持IE11及以下版本，官方单独提供了兼容的[polyfill](https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill)。用法如下：

```
cnpm install react-app-polyfill -D
```

or

```
yarn add react-app-polyfill
```

根据要支持的IE最小版本选择，低版本包括对高版本的兼容：

```js
import 'react-app-polyfill/ie9';
```

or

```js
import 'react-app-polyfill/ie11';
```

> polyfill必须放在CRA2项目的 `src/index.js`的第一行。

针对ie9的polyfill，在开发环境中会报`“Map”未定义`错误，因为webpack-dev-server不支持，想起参见[isure#1583](https://github.com/webpack/webpack-dev-server/issues/1583)， create-react-app的[isure#4674](https://github.com/facebook/create-react-app/issues/5674) 。生产环境可以正常使用。



#### ​通过Babel Presets自动引入

​	通过在Babel配置中，设置Presets的`useBuiltIns: 'usage'`，开启智能导入，即当前js文件使用的特性会自动import，并且整个项目中相同的polyfill只会加载1次。 [具体用法参考](https://babeljs.io/docs/en/babel-preset-env)

```json
// .babelrc
{
    "presets": [
        [
            "@babel/env",
            {
                "useBuiltIns": "usage",
                "targets": {
                    "browsers": [
                        "Chrome >= 49",
                        "Firefox >= 45",
                        "Safari >= 10",
                        "Edge >= 13",
                        "IE > 8",
                        "iOS >= 10",
                        "Electron >= 0.36"
                    ]
                }
            }
        ],
        [
            "@babel/react",
            {
                "useBuiltIns": true
            }
        ],
        "react-app"
    ],
    "plugins": [
        [
            "import",
            {
                "libraryName": "lodash-es",
                "libraryDirectory": "",
                "camel2DashComponentName": false
            }
        ],
        ["@babel/plugin-transform-runtime", { "corejs": 2 }],
        ["@babel/plugin-proposal-decorators", { "legacy": true }],
        ["@babel/plugin-proposal-class-properties", { "loose": true }]
    ]
}
```



### Polyfill.io API按需引入

​	随着浏览器技术的更新，大多数ES6特性都得到了很好的支持，采用Babel转码，会强制新的浏览器运行旧代码，这并不高效。 [Polyfill.io API项目通过polyfill 方式在客户端直接执行 ES6 代码。

![img](http://p9.qhimg.com/t0109387db34edbeb01.png)

##### **实现原理**

​	Polyfill.io 读取每个请求的 [User-Agent](https://en.wikipedia.org/wiki/User_agent)(UA) 头，并生成适合于该浏览器的 polyfill ，基于你的应用所使用的特性发回必要的代码。该项目由[Financial Times](http://www.ft.com/) 在开发和维护这个项目，生命力有保障。

>Polyfill.io 没有提供语法糖支持。比如 类、增强的对象字面量，以及箭头函数之类的特性。对那些代码，你仍然需要进行编译。

##### 配置 Polyfill.io

​	配置方法非常简单，在项目的入口页面文件，如`public/index.html`中，将托管在 CDN 的脚本添加到你的页面上：

```html
// public/index.html
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
```

​	运行脚本时，将返回 UA 和你想要的特性。

```
UA detected: chrome/56.0.0
Features requested: default
```

##### 修改请求参数

​	它提供了[一堆选项](https://polyfill.io/v2/docs/api) 来自定义你要返回的特性。

###### Features

​	该参数指定需要 polyfill 的浏览器特性。多个特性名之间用逗号分隔。允许使用的特性明在 浏览器和特性 页中列出。

```html
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=fetch"></script>
```

​	在 Safari 10 下，脚本返回内容如下：

	Features requested: fetch
	
		- setImmediate, License: CC0 (required by "Promise", "fetch")
		- fetch	
  	如果一个特性，比如 fetch 依赖于另一个特性比如 Promise，Polyfill.io 会自动加载依赖。

###### Flags

+ always - Polyfill 将始终被包含，不管 UA 中指出的浏览器是否已经支持该特性。

+ gated - 通过特性检测来判断 Polyfill，只有在浏览器原生 API 不支持这些特性的情况下才返回并执行 Polyfill。

  ```html
  <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=fetch&flags=gated"></script>
  ```

###### Callback

  Polyfill 脚本加载完成之后要执行的函数名。这是最简单的在 polyfill 加载完成后触发你自己的代码的方式，这样 polyfill 服务可以更容易通过 async 和 defer 属性被异步加载。

##### 存在的问题

听起来很好，但是仍然不完美。

最新的浏览器不需要加载 ES5 代码了，但是还需要通过服务器请求来检测是否需要 polyfill。



### dynamic polyfill

Polyfill.io 项目需要通过服务器请求来检测是否需要 polyfill。

`dynamic polyfill`项目进行了优化，它在发起任何服务端请求前检测特性是否已经被原生支持。配置如下：

```js
import polyfill from 'dynamic-polyfill'
 
polyfill({
    fills: 'fetch, Promise',
    options: 'gated', // default: null
    minify: false,  // default: true
    afterFill() {
        main()
    }
})
 
function main() {
    // app code here
}
```

执行过程如下：

检测 [window.fetch, window.Promise] 等特性是否存在。

-如果存在，运行 afterFill() 回调。

-如果它们不存在，创建一个 <script> 标签，并且包含 async 属性，将 Polyfill.io 链接插入，用参数中提供的选项去请求，并在它加载完成后执行 afterFill() 回调。

> 注意： 现在还没有支持全部选项，只有那些最重要的项被支持。

这个模块在压缩后不到 1KB 大小，而且没有任何依赖，对项目使用来说成本超低。