今天是 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.assign、Array.prototype.includes),而 Polyfill 正是解决这个问题的关键。我会基于这篇文章的核心内容,梳理出更清晰的逻辑、实战配置和选型建议,帮你彻底搞懂前端 Polyfill 方案。
一、核心概念:为什么需要 Polyfill?
1. Babel 编译 vs Polyfill 的本质区别
| 类型 |
作用范围 |
处理对象 |
示例 |
| Babel 编译 |
语法层面 |
ES6+ 新语法(无新 API 调用) |
箭头函数 → 普通函数、let/const → var、解构赋值 → 普通赋值 |
| Polyfill |
API 层面 |
ES6+ 新增的全局/原型方法 |
Object.assign、Array.prototype.includes、Promise、fetch |
简单来说:
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);
"use strict"; var foo = function foo(a, b) { return Object.assign(a, b); };
"use strict";
if (!Object.assign) { Object.assign = function(target, ...sources) { }; } var foo = function foo(a, b) { return Object.assign(a, b); };
|
2. Polyfill 的核心目标
- 让 IE9+/老旧浏览器支持 ES2015+ 全部特性
- 最小化代码体积(避免全量引入)
- 不污染全局环境(库/框架开发场景)
二、主流 Polyfill 方案对比(附实战配置)
核心逻辑
针对单个 API 引入专属插件,编译时将 API 替换为内联的兼容代码。
实战配置(以 Object.assign 为例)
1 2
| yarn add -D babel-plugin-transform-object-assign
|
1 2 3 4 5
| { "presets": ["latest"], "plugins": ["transform-object-assign"] }
|
编译结果
1 2 3 4 5
| var _extends = function (target) { }; var foo = function foo(a, b) { return _extends(a, b); };
|
优缺点
| 优点 |
缺点 |
| 精准补全,体积小 |
需手动逐个添加插件(用多少加多少,繁琐) |
| 无全局污染 |
多文件使用同一 API 时,代码重复内联,体积膨胀 |
| - |
无法处理非直接调用(如 const o = Object; o.assign(a, b)) |
核心逻辑
从 babel-runtime 包中统一引入 Polyfill 方法(而非内联),避免代码重复,且不修改全局对象。
实战配置
1 2 3 4
| yarn add -D babel-plugin-transform-runtime
yarn add babel-runtime
|
1 2 3 4 5
| { "presets": ["latest"], "plugins": ["transform-runtime"] }
|
编译结果
1 2 3 4 5
| var _assign = require("babel-runtime/core-js/object/assign"); var foo = function foo(a, b) { return _assign(a, b); };
|
优缺点
| 优点 |
缺点 |
| 代码不重复,体积优化 |
无法处理非直接调用的 API(如 o.assign) |
| 不污染全局环境(适合库开发) |
无法补齐原型方法(如 Array.prototype.includes) |
| 无需手动加插件,自动引入 |
- |
方案 3:babel-polyfill(全量全局注入,应用开发首选)
核心逻辑
直接注入 ES2015+ 所有 API 的兼容实现(修改全局对象),打造完整的 ES6 运行环境。
实战配置
1 2
| yarn add babel-polyfill
|
1 2 3 4 5 6
| import 'babel-polyfill';
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
| { "presets": [ ["env", { "targets": { "browsers": ["IE >= 9", "Chrome >= 40"] }, "useBuiltIns": true }] ] }
|
1 2
| 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
| <script src="https://cdn.polyfill.io/v2/polyfill.js"></script>
<script src="https://cdn.polyfill.io/v2/polyfill.js?features=Object.assign,Array.prototype.includes"></script>
<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 + 兜底方案 |
动态适配,最新浏览器零体积 |
关键注意点
- **库开发禁止使用
babel-polyfill**:会污染全局对象,导致使用者的代码出现兼容性问题;
- **
useBuiltIns 必须配合 import 'babel-polyfill'**:否则不会触发按需注入;
- polyfill.io 需做兜底:如果 CDN 失效,需降级到本地 Polyfill;
- 避免重复引入:
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
| { "presets": [ ["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3, "targets": { "ie": "9" } }] ] }
|
2. es-shims 系列(精准补全单个 API)
如果只需要补全个别 API,可直接引入专门的 shim 包:
1 2 3 4
| import 'object-assign';
import 'es6-promise/auto';
|
总结
关键点回顾
- 核心区别:Babel 编译语法,Polyfill 补齐 API,二者结合才能实现 ES6+ 全兼容;
- 方案选型:应用开发用
babel-preset-env + core-js(按需),库开发用 transform-runtime(无全局污染),极致优化用 polyfill.io;
- 核心原则:最小化体积(按需注入)、适配目标浏览器、避免全局污染(库场景)。
Polyfill 的核心是「兼容」与「体积」的平衡,没有最优方案,只有最适合当前场景的方案——业务应用优先保证兼容性和开发效率,库/框架优先保证无侵入性和体积优化。