Skip to content

JavaScript 兼容性

来源

在提出解决JavaScript兼容性问题方案开始前,回顾JavaScript的发展史,以明确知道JavaScript兼容性问题的来源。

1995年: JavaScript 由 Brendan Eich 在网景公司(Netscape)发明,最初被称为 Mocha,后来改名为 LiveScript,最终定名为 JavaScript。这种命名是为了利用当时 Java 语言的流行。

1996年: Netscape Navigator 2.0 中首次包含了 JavaScript。微软很快注意到 JavaScript 的潜力,并在其 Internet Explorer 3.0 中推出了 JScript,这是 JavaScript 的一个变体。

1997年: ECMA 国际组织制定了 JavaScript 的标准,并发布了 ECMAScript 1.0(简称 ES1),JavaScript 成为一种标准化的脚本语言。

1999年: ECMAScript 3(ES3)发布,这是一个重要的版本,引入了正则表达式、try/catch 异常处理等。

2000年代初: 随着浏览器战争(Browser Wars)的进行,不同浏览器对 JavaScript 的支持出现了分歧,导致了“跨浏览器兼容性”的问题。

2005年: Jesse James Garrett 提出了 Ajax(Asynchronous JavaScript and XML)概念,这一技术使得网页可以在不重新加载页面的情况下与服务器通信,极大推动了 Web 应用的发展。

2009年: ECMAScript 5(ES5)发布,这是继 ES3 之后的一个重要版本,引入了严格模式(Strict Mode)、JSON 支持、新的数组方法等。

2009年: Node.js 诞生,由 Ryan Dahl 开发,允许 JavaScript 在服务器端运行,推动了 JavaScript 全栈开发的潮流。

2010年左右: 各种 JavaScript 框架如 AngularJS、React、Vue.js 开始兴起,推动了前端开发的变革。

2015年: ECMAScript 6(ES6,也称 ES2015)发布,这是 JavaScript 发展史上的一个重大里程碑。ES6 引入了许多重要的新特性,如块级作用域、箭头函数、类、模板字符串、模块化等,使 JavaScript 成为一种更加现代化和强大的编程语言。

2016年-至今: ECMAScript 标准开始逐年更新,发布了 ES2016、ES2017 等,每年引入一些新特性和改进。

从中可以看出JavaScript兼容性问题主要在于浏览器环境的JavaScript的支持,包括不限于:JavaScript的新语法、浏览器对JavaScript的API的实现(ECMAScript标准、DOM/BOM)等

可以参考 Can I use 来查询(市面主要)浏览器对API的支持

解决

使用最多、共识的是编译器babel,但现代前端开发的新兴工具还有:swcoxc

它们的作用:将JavaScript的语法降级,缺失API模拟(core-js实现),甚至可以通过一些插件处理CSS的兼容性(JavaScript去操作CSS,做样式降级或者自动补全 -webkit- 前缀等)、将JavaScript中新的提案语法降级成标准化语法等。这样就解决了JavaScript的兼容性问题

下面会详细介绍下babel

babel的语意也叫做“巴别塔”,来源于希伯来语,巴别塔象征的统一的国度、统一的语言。

01_01

而JavaScript的世界缺少一座巴别塔,不同版本的浏览器能识别的ES标准并不相同,就导致了开发者面对不同版本的浏览器要使用不同的语言,和古巴比伦一样,前端开发也面临着这样的困境。

它的出现就是用于解决这样的问题,它是一个编译器,可以把不同标准书写的语言,编译为统一的、能被各种浏览器识别的语言。

01_02

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

01_03

Babel

官网:babel,中文网:babel中文网

babel playground:https://babeljs.io/repl

使用

单独使用babel,需要使用到@babel/cli@babel/core这两个库。

@babel/cli:babel核心库,提供了编译所需的所有api

@babel/core:提供一个命令行工具,调用核心库的api完成编译

  1. 安装:

    shell
    pnpm add -D @babel/core @babel/cli
  2. 使用@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

  3. 使用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配置文件中的preset就是配置预设的地方,需要注意的是babel预设的加载是有顺序的,是从后往前的,如果配置的预设有使用到同一个插件,那么最新的会覆盖旧的。

这里详细讲一下babel的 @babel/preset-env 预设,也是最常用的预设。

作用:使用最新的JavaScript,而无需微观管理目标环境需要哪些语法转换API转换。这既让你的生活更轻松,也让 JavaScript 包更小!

转换类型处理对象配置工具示例
语法转换新语法(ES6+ 语法特性)@babel/preset-env + 语法插件箭头函数、类、解构、可选链 ?.
API 转换新 API(内置对象/方法)core-js + useBuiltIns 配置PromiseArray.includes()

在这一节的配置项就涉及到了API转换,至于语法转换在下一节babel插件中涉及

安装:

shell
pnpm add -D @babel/preset-env

配置项:

json
{
	"preset": [
        [
            "@babel/preset-env",
            {
            	// 配置项 假设下面配置存在
				a: "a1"
            }
        ]
    ]
}

@babel/preset-env 预设也对browserslist进行了集成,可以通过选项targets来配置(需要将ignoreBrowserslistConfig置为true),如:

json
{
    "preset": [
        [
            "@babel/preset-env",
            {
                "ignoreBrowserslistConfig": true, 
                "targets": { 
                    "chrome": "58", 
                    "ie": "11"
                } 
            }
        ]
    ]
}

但通常情况下,会使用browserslist指定的配置文件,往往是.browserslistrc文件,因为工程化生态系统中,像postcss、stylelint等一些工具也会使用到它,如:

last 10 version
> 1%
not ie <= 8
那browserslist是什么?

它的作用是通过其配置文件去涵盖你想要兼容的浏览器范围,它的语法使用需要参考:Browserslist,下面使用一个示例(语法使用在左侧有文档):

那么接下来讲一下这个预设常用的几个配置项:

  1. usebuiltins

    用于如何处理 polyfill,默认为 false,也就是禁用 polyfill。使用"usage"或者"entry"才会引入 core-js 作为polyfill,而"usage"和"entry"的区别在于:"usage"是按需引入(仅添加代码中实际用到的 API 的 polyfill),"entry"是全量引入(根据目标浏览器,一次性引入所有可能缺失的 polyfil)

  2. core-js

    一般情况下传递一个对象,如果是字符串代表版本,如:"core-js": "3.41"

    version: 指定core-js的版本

    shippedProposals: 是否启用浏览器中发布了一段时间的提案polyfill和转换

  3. modules

    这个配置将启用将 ES 模块语法转换为另一种模块类型,将此设置为 false 将保留 ES 模块。仅当你打算将原生 ES 模块发送到浏览器时才使用此选项。如果你使用 Babel 的 bundler,默认的 modules: "auto" 总是首选。

    这个配置可以是 "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false

  4. ignoreBrowserslistConfig

    用于指示 Babel 是否忽略项目中的 Browserslist 配置文件,默认为false

    如果启用(true)时,通常和target搭配

什么是 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预设相反),且插件是在预设之前运行

插件的选项配置和预设类似:

json
{
    // ...
    "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"] }]
        ]
    }