JavaScript 兼容性
来源
JavaScript 兼容性问题的根本原因在于各大浏览器厂商在实现 JavaScript 时的差异。为了解决这种混乱,ECMA 国际组织于 1997 年制定了 ECMAScript(ECMA-262)标准,由 TC39 委员会 负责维护和推进。ECMAScript 规范定义了 JavaScript 的语法、类型、语句、关键字、操作符和内置对象等内容,推动了 JavaScript 语言的标准化和持续发展。
虽然 ECMAScript 标准每年持续更新,引入新特性和改进,但由于各浏览器对新标准的支持进度不同,兼容性问题依然存在。开发者通常需要借助如 Can I use 等工具查询各浏览器对语法和 API 的支持情况,并通过 Babel 等工具进行语法降级和 polyfill 处理,以实现跨浏览器兼容。
从中可以看出,JavaScript 兼容性问题主要在于浏览器环境对 JavaScript 的支持,包括但不限于:新语法、API 的实现(ECMAScript 标准、DOM/BOM 等)。可以参考 Can I use 来查询主流浏览器对 API 的支持情况。
解决
使用最多、共识的是编译器babel,但现代前端开发的新兴工具还有:swc、oxc
它们的作用:将 JavaScript 的语法降级,缺失 API 模拟(core-js 实现),甚至可以通过一些插件处理 CSS 的兼容性(JavaScript 去操作 CSS,做样式降级或者自动补全 -webkit-
前缀等)、将 JavaScript 中新的提案语法降级成标准化语法等。这样就解决了 JavaScript 的兼容性问题
下面会详细介绍下 babel
babel 的语意也叫做“巴别塔”,来源于希伯来语,巴别塔象征的统一的国度、统一的语言。

而 JavaScript 的世界缺少一座巴别塔,不同版本的浏览器能识别的 ES 标准并不相同,就导致了开发者面对不同版本的浏览器要使用不同的语言,和古巴比伦一样,前端开发也面临着这样的困境。
它的出现就是用于解决这样的问题,它是一个编译器,可以把不同标准书写的语言,编译为统一的、能被各种浏览器识别的语言。

babel 的转化方式灵活多样,与构建工具、postcss 有些类似,转化需要依靠插件实现,本身提供语法分析的功能。

Babel
babel测试工具:babel训练场
使用
单独使用 babel,需要使用到@babel/cli
和@babel/core
这两个库。
@babel/cli
:babel 核心库,提供了编译所需的所有 api
@babel/core
:提供一个命令行工具,调用核心库的 api 完成编译
安装:
shellpnpm add -D @babel/core @babel/cli
使用
@babel/cli
编译:shell# 按文件编译,--out-file 可以缩写为 -o, npx babel target.js --out-file output.js # 案目录编译, --out-dir 可以缩写为 -d npx babel src --out-dir lib
其中还可以指定参数:
--watch(可以缩写为-w)可以在每次更改文件时编译文件
--source-maps(可以缩写为-s)来添加映射文件
更多命令行参数参考:docs babel-cli
使用 babel 插件
一般情况下,我们使用 babel 的大部分功能是基于插件的,要启用这个功能需要
使用 babel 的配置文件(babel 编译的入口)
可以是
babel.config.json
、.babel.json
、.babelrc
,甚至可以在package.json
中配置选项配置文件内容结构类似于:
json{ "presets": [...], "plugins": [...] }
根据经验、文档、插件仓库(自己手写也行)选择插件或者预设
大部分情况下都会使用的到:@babel/preset-env 这个预设
如果说你用框架是 react,使用 babel 时会用到 @babel/preset-react , 这个主要针对于 react ,比如它的 jsx 文件
还有 TypeScript,使用 babel 时会用到 @babel/preset-typescript。
插件仓库主要是一些语法转换插件(解析新的或者实验性 JavaScript 语法转换成向后兼容的 JavaScript 语法)、模块格式转化插件、预设插件等,地址:插件列表
什么是预设呢?
预设是多个插件的集合,配置预设可以节省必要插件的安装和配置项
启用 babel 实时编译(自定义命令行参数)
shell# 示例 npx babel src -d dist -w
babel预设
babel预设是一组插件的集合。
babel 配置文件中的preset
就是配置预设的地方,需要注意的是 babel 预设的加载是有顺序的,是从后往前的,如果配置的预设有使用到同一个插件,那么最新的会覆盖旧的。
这里详细讲一下 babel 的 @babel/preset-env
预设,也是最常用的预设。下面是该预设包含的插件(截至2025年babel官网):

作用:使用最新的 JavaScript,而无需微观管理目标环境需要哪些语法转换和API 转换。这既让你的生活更轻松,也让 JavaScript 包更小!
转换类型 | 处理对象 | 配置工具 | 示例 |
---|---|---|---|
语法转换 | 新语法(ES6+ 语法特性) | @babel/preset-env + 语法插件 | 箭头函数、类、解构、可选链 ?. |
API 转换 | 新 API(内置对象/方法) | core-js + useBuiltIns 配置 | Promise 、Array.includes() |
在这一节的配置项就涉及到了API 转换,至于语法转换在下一节babel 插件中涉及
预设@babel/preset-env
会用上所有插件吗🧐
实际上不会,该预设会根据所包含的浏览器,自动选用插件集合,这个策略取决插件转换的语法在包含的浏览器中是否有必要。很显然,这个策略会非常合理,毕竟如果一种JavaScript语法能够在所包含的浏览器上运行,其转换这个语法的插件也没有必要使用。
这里提到的"预设会根据所包含的浏览器",后面可以根据该预设的配置项配置。
安装:
pnpm add -D @babel/preset-env
配置项:
{
"preset": [
[
"@babel/preset-env",
{
// 配置项 假设下面配置存在
"a": "a1"
}
]
]
}
@babel/preset-env 预设也对browserslist进行了集成,可以通过选项 targets 来配置(需要将ignoreBrowserslistConfig
置为 true),如:
{
"preset": [
[
"@babel/preset-env",
{
"ignoreBrowserslistConfig": true,
"targets": {
"chrome": "58",
"ie": "11"
}
}
]
]
}
但通常情况下,会使用browserslist指定的配置文件,往往是.browserslistrc
文件,因为工程化生态系统中,像 postcss、stylelint 等一些工具也会使用到它,如:
last 10 version
> 1%
not ie <= 8
那么接下来讲一下这个预设常用的几个配置项:
usebuiltins
用于如何处理 polyfill,默认为 false,也就是禁用 polyfill。使用"usage"或者"entry"才会引入 core-js 作为 polyfill,而"usage"和"entry"的区别在于:"usage"是按需引入(仅添加代码中实际用到的 API 的 polyfill),"entry"是全量引入(根据目标浏览器,一次性引入所有可能缺失的提案 polyfill)
core-js
一般情况下传递一个对象,如果是字符串代表版本,如:
"core-js": "3.41"
version: 指定
core-js
的版本shippedProposals: 是否启用浏览器中发布了一段时间的提案 polyfill 和转换
modules
这个配置将启用将 ES 模块语法转换为另一种模块类型,将此设置为
false
将保留 ES 模块。仅当你打算将原生 ES 模块发送到浏览器时才使用此选项。如果你使用 Babel 的 bundler,默认的modules: "auto"
总是首选。这个配置可以是 "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
ignoreBrowserslistConfig
用于指示 Babel 是否忽略项目中的 Browserslist 配置文件,默认为 false
如果启用(true)时,通常和 target 搭配
includes 和 exclude
includes 选项指定一个字符串数组,每一个字符串元素也就是babel插件的名称,强制使用每个babel插件,可选用的有效字符串:babel插件 和 内置插件(核心 js@3 和 核心 js@2)
exclude 选项指定一个始终排除/删除的插件数组。可能的选项与上面的include选项相同
什么是 polyfill(垫片)?
它是指一段 JavaScript 代码,它用于在现代浏览器中“模拟”或“填充”哪些旧版本浏览器原生不支持的 JavaScript API、HTML 5 功能或者 CSS 特性
polyfill(垫片)的目的是:让开发者能够使用最新的 Web 标准特性进行开发,同时确保这些特性在老旧或不支持它们的浏览器中也能基本正常运行,从而提升跨浏览器的兼容性。
比如从 Can I use 查找 Array.prototype.with API(ES2023 新出的):

发现我们需要兼容 IE 和 一些不知道是否实现的其他浏览器(如 UC Browser for Android 等),我通过 babel 引入 polyfill,查看编译后结果:

但是在使用 babel 的 polyfill 需要注意一点,就是图片上core-js
做 polyfill 的库,它是对 ECMAScript 标准 API做垫片处理,不对 Web API 做垫片处理(比如:浏览器环境的 fetch)
其他的预设感兴趣可以自己去探索:babel presets
这里还推荐一个预设babel-minify可以减少构建后的 JavaScript 体积
babel插件
babel 配置文件中的plugins
就是配置插件的地方,同样插件的加载也是有顺序的,是从前往后(与babel预设
相反),且插件是在预设之前运行。
插件的选项配置和预设类似:
{
// ...
"plugins": [
[
// 插件名
"xxx",
{
// 插件选项
"option1": "val1",
"option2": "val2"
}
]
]
}
下面是一些有意思的插件:
babel-plugin-transform-remove-console
该插件会移除源码中的打印的日志
配置项:
exclude
:排除的 console api 方式如:
json{ "plugins": [ // console.error 和 console.warn 不会被插件移除 [ "babel-plugin-transform-remove-console", { "exclude": ["error", "warn"] }, ] ] }
@babel/plugin-transform-json-modules
这个插件可以将ES2025的新特性,作用是将
import ... with { type: "json" }
声明转换为特定于平台的 API 以读取然后JSON.parse
导入的文件。在示例中babel训练场会将这种语法转换成Browsers和node.js都兼容的模式:
使用:
json{ "plugins": [ ["@babel/plugin-transform-json-modules"], ] }
如果需要转换成其他的CommonJS等语法规范,可以设置
@babel/preset-env
预设的modules
选项。@babel/plugin-transform-class-properties
这个插件是ES2022的新特性,可以转换四种类属性的写法,降级为兼容的语法:
- 类属性被赋值为固定值
- 类属性被赋值为箭头函数
- 静态类属性被赋值为固定值
- 静态类属性被赋值为函数表达式
可以在babel训练场看到这四种写法和经过该插件编译后的结果:
结合@babel/preset-env使用:
json{ "preset-env": [ [ "@babel/preset-env", { // 省略其他。。。。。 "include": [ // 全称为 "@babel/plugin-transform-class-properties",下面是简写 "transform-class-properties", ] } ] ] }
@babel/plugin-transform-private-property-in-object
这个插件是ES2022的新特性,可以转换类中的私有元素,降级为兼容的语法。
当今环境大部分浏览器都支持了私有元素,所以在babel训练场编译的时候,需要加入该语法不兼容的浏览器才能转换为兼容的语法。
在babel训练场可以看到私有属性经过该插件编译后的结果:
json{ "preset-env": [ [ "@babel/preset-env", { // 省略其他。。。。。 "include": [ // 全称为 "@babel/plugin-transform-private-property-in-object",下面是简写 "transform-private-property-in-object", ] } ] ] }
代码示例
请参考/concept/Babel
子仓库