前端 Polyfill 方案

今天是 2017 年 7 月 7 日,es2015 正式发布已经两年了。但最新的浏览器们逼近 100% 的支持率对我们好像并没有什么卵用,为了少数用户的体验,我们很可能需要兼容 IE9。感谢 babel 的编译,让我们完美的提前使用上了 const,let 和 arrow function。可也许你还是面对着不敢直接使用 fetch 或是 Object.assign 的难题? babel 和 polyfill 刚接触 babel 的同学一开始可能都认为在使用了 babel 后就可以无痛的使用 es2015 了,之后被各种 undefined 的报错无情打脸。一句话概括, babel 的编译不会做 polyfill。

前端 Polyfill 方案全解析(从原理到最佳实践)

你提到的这篇关于 Polyfill 的文章精准戳中了前端兼容的核心痛点——Babel 只能编译语法(如箭头函数、let/const),却无法补齐 ES2015+ 新增的 API(如 Object.assignArray.prototype.includes),而 Polyfill 正是解决这个问题的关键。我会基于这篇文章的核心内容,梳理出更清晰的逻辑、实战配置和选型建议,帮你彻底搞懂前端 Polyfill 方案。

一、核心概念:为什么需要 Polyfill?

1. Babel 编译 vs Polyfill 的本质区别

类型 作用范围 处理对象 示例
Babel 编译 语法层面 ES6+ 新语法(无新 API 调用) 箭头函数 → 普通函数、let/const → var、解构赋值 → 普通赋值
Polyfill API 层面 ES6+ 新增的全局/原型方法 Object.assignArray.prototype.includesPromisefetch

简单来说:
Babel 让老浏览器「能解析」新语法,Polyfill 让老浏览器「有」新 API
比如这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 原代码
const foo = (a, b) => Object.assign(a, b);
// Babel 编译后(仅处理语法,API 保留)
"use strict";
var foo = function foo(a, b) {
return Object.assign(a, b); // Object.assign 仍存在,IE9 不认识会报错
};
// 加 Polyfill 后(补齐 API)
"use strict";
// 先注入 Object.assign 的兼容实现
if (!Object.assign) {
Object.assign = function(target, ...sources) { /* 兼容逻辑 */ };
}
var foo = function foo(a, b) {
return Object.assign(a, b); // 此时 IE9 能识别
};

2. Polyfill 的核心目标

  • 让 IE9+/老旧浏览器支持 ES2015+ 全部特性
  • 最小化代码体积(避免全量引入)
  • 不污染全局环境(库/框架开发场景)

二、主流 Polyfill 方案对比(附实战配置)

方案 1:babel-plugin-transform-xxx(按需插件,原始方案)

核心逻辑

针对单个 API 引入专属插件,编译时将 API 替换为内联的兼容代码。

实战配置(以 Object.assign 为例)

1
2
# 安装插件
yarn add -D babel-plugin-transform-object-assign
1
2
3
4
5
// .babelrc
{
"presets": ["latest"],
"plugins": ["transform-object-assign"]
}

编译结果

1
2
3
4
5
// 内联替换 Object.assign 为自定义 _extends 方法
var _extends = function (target) { /* 兼容逻辑 */ };
var foo = function foo(a, b) {
return _extends(a, b); // 不再依赖全局 Object.assign
};

优缺点

优点 缺点
精准补全,体积小 需手动逐个添加插件(用多少加多少,繁琐)
无全局污染 多文件使用同一 API 时,代码重复内联,体积膨胀
- 无法处理非直接调用(如 const o = Object; o.assign(a, b)

方案 2:babel-runtime + babel-plugin-transform-runtime(无全局污染,库开发首选)

核心逻辑

babel-runtime 包中统一引入 Polyfill 方法(而非内联),避免代码重复,且不修改全局对象。

实战配置

1
2
3
4
# 开发依赖(编译时插件)
yarn add -D babel-plugin-transform-runtime
# 生产依赖(运行时核心库)
yarn add babel-runtime
1
2
3
4
5
// .babelrc
{
"presets": ["latest"],
"plugins": ["transform-runtime"]
}

编译结果

1
2
3
4
5
// 从 babel-runtime 引入 assign 方法(统一复用)
var _assign = require("babel-runtime/core-js/object/assign");
var foo = function foo(a, b) {
return _assign(a, b); // 不修改全局 Object.assign
};

优缺点

优点 缺点
代码不重复,体积优化 无法处理非直接调用的 API(如 o.assign
不污染全局环境(适合库开发) 无法补齐原型方法(如 Array.prototype.includes
无需手动加插件,自动引入 -

方案 3:babel-polyfill(全量全局注入,应用开发首选)

核心逻辑

直接注入 ES2015+ 所有 API 的兼容实现(修改全局对象),打造完整的 ES6 运行环境。

实战配置

1
2
# 安装
yarn add babel-polyfill
1
2
3
4
5
6
// 方式 1:入口文件顶部引入(推荐)
import 'babel-polyfill'; // 必须在所有业务代码之前
// 方式 2:webpack entry 配置
module.exports = {
entry: ['babel-polyfill', './src/index.js']
};

优缺点

优点 缺点
一键补齐所有 API,无需关注细节 体积大(未压缩 251kb,压缩后 86kb)
支持所有场景(包括原型方法、非直接调用) 污染全局环境(不适合库/框架开发)
适配 IE9+ 所有 ES6+ API 全量引入,即使只用到一个 API 也会加载全部

方案 4:babel-preset-env + useBuiltIns(按需注入,最优应用方案)

核心逻辑

基于 babel-preset-env 识别目标浏览器,自动只引入需要的 Polyfill(替代全量 babel-polyfill)。

实战配置

1
2
3
# 安装
yarn add babel-polyfill
yarn add -D babel-preset-env
1
2
3
4
5
6
7
8
9
10
11
// .babelrc
{
"presets": [
["env", {
"targets": {
"browsers": ["IE >= 9", "Chrome >= 40"] // 目标兼容浏览器
},
"useBuiltIns": true // 开启按需注入
}]
]
}
1
2
// 入口文件(仅需引入一次,会被替换为按需 Polyfill)
import 'babel-polyfill';

效果对比

目标浏览器 最终 Polyfill 体积
IE >= 9 ~189kb(未压缩)
Chrome >= 59 ~30kb(未压缩)

优缺点

优点 缺点
按需注入,体积大幅优化 仍污染全局环境(仅适合应用开发)
自动识别目标浏览器,无需手动配置 需明确指定兼容的浏览器范围
保留 babel-polyfill 的全场景支持 -

方案 5:polyfill.io(动态按需加载,极致优化)

核心逻辑

通过 CDN 动态返回适配当前浏览器的 Polyfill(基于 UA 识别),最新浏览器返回空文件,老旧浏览器返回所需 Polyfill。

实战配置

1
2
3
4
5
6
<!-- 方式 1:基础用法(自动识别浏览器) -->
<script src="https://cdn.polyfill.io/v2/polyfill.js"></script>
<!-- 方式 2:指定需要的 API -->
<script src="https://cdn.polyfill.io/v2/polyfill.js?features=Object.assign,Array.prototype.includes"></script>
<!-- 方式 3:指定目标浏览器 -->
<script src="https://cdn.polyfill.io/v2/polyfill.js?ua=IE/9"></script>

优缺点

优点 缺点
体积最优(不同浏览器加载不同内容) 依赖第三方 CDN,存在可用性风险
无需编译,直接引入 国内浏览器 UA 识别可能不准确
不污染项目构建流程 无法离线使用,需考虑网络问题

三、场景化选型指南(关键决策依据)

开发场景 推荐方案 核心原因
业务应用开发(兼容 IE9+) babel-preset-env + useBuiltIns 按需注入,体积可控,全场景支持
库/框架开发(如组件库、SDK) babel-runtime + transform-runtime 不污染全局环境,避免影响使用者
小型项目(仅用到个别 API) babel-plugin-transform-xxx 体积最小,精准补全
大型应用(追求极致体积) polyfill.io + 兜底方案 动态适配,最新浏览器零体积

关键注意点

  1. **库开发禁止使用 babel-polyfill**:会污染全局对象,导致使用者的代码出现兼容性问题;
  2. **useBuiltIns 必须配合 import 'babel-polyfill'**:否则不会触发按需注入;
  3. polyfill.io 需做兜底:如果 CDN 失效,需降级到本地 Polyfill;
  4. 避免重复引入babel-polyfill 只需引入一次,多次引入会重复加载。

四、进阶优化:现代 Polyfill 方案(2024+ 补充)

随着前端工程化的发展,上述经典方案有了更优替代:

1. core-js(babel-polyfill 底层依赖,新一代 Polyfill 核心)

babel-polyfill 本质是 core-js + regenerator-runtime,直接使用 core-js 更灵活:

1
2
3
4
5
6
7
8
9
10
11
12
// .babelrc(babel 7+ 配置)
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage", // 自动按需注入(无需手动 import core-js)
"corejs": 3, // 指定 core-js 版本
"targets": {
"ie": "9"
}
}]
]
}

2. es-shims 系列(精准补全单个 API)

如果只需要补全个别 API,可直接引入专门的 shim 包:

1
2
3
4
// 仅引入 Object.assign 的兼容实现
import 'object-assign';
// 仅引入 Promise 的兼容实现
import 'es6-promise/auto';

总结

关键点回顾

  1. 核心区别:Babel 编译语法,Polyfill 补齐 API,二者结合才能实现 ES6+ 全兼容;
  2. 方案选型:应用开发用 babel-preset-env + core-js(按需),库开发用 transform-runtime(无全局污染),极致优化用 polyfill.io
  3. 核心原则:最小化体积(按需注入)、适配目标浏览器、避免全局污染(库场景)。

Polyfill 的核心是「兼容」与「体积」的平衡,没有最优方案,只有最适合当前场景的方案——业务应用优先保证兼容性和开发效率,库/框架优先保证无侵入性和体积优化。


前端 Polyfill 方案
https://cszy.top/2017-08-25 前端-polyfill-方案/
作者
csorz
发布于
2017年8月25日
许可协议