上一讲中, 我们介绍了前端模块化的发展历史 一起梳理了一下
CommonJS
,AMD
,CMD
以及UMD
与ESM
等模块化规范的发展以及含义. 本章开始, 我们则从JS
语言出发, 了解并学习其背后的ECMAScript
规范以及 编译器Babel
1. ECMAScript 与 JavaScript 的关系
ECMAScript
是JavaScript
的语言标准, 是有版本迭代的, 比如我们耳熟能详的ES6
就是ECMAScript
的第6个版本(也就 ES2015
即2015年的版本), 当然ECMAScript
标准还有其他的实现, 如NodeJS``JScript``ActionScript
等.
引自 https://es6.ruanyifeng.com/#docs/intro
在
1996年11月
,JavaScript
的创造者公司Netscape
, 决定将JavaScript
提交给标准化组织 ECMA, 希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。 该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。ECMAScript 1.0 是 1997 年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998 年 6 月)和 ECMAScript 3.0(1999 年 12 月)。3.0 版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习 JavaScript,其实就是在学 3.0 版的语法。
2000 年,ECMAScript 4.0 开始酝酿。这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是 2000 年。
为什么 ES4 没有通过呢?因为这个版本太激进了,对 ES3 做了彻底升级,导致标准委员会的一些成员不愿意接受。ECMA 的第 39 号技术专家委员会(Technical Committee 39,简称 TC39)负责制订 ECMAScript 标准,成员包括 Microsoft、Mozilla、Google 等大公司。
2007 年 10 月,ECMAScript 4.0 版草案发布,本来预计次年 8 月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以 Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动;以 JavaScript 创造者 Brendan Eich 为首的 Mozilla 公司,则坚持当前的草案。
2008 年 7 月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA 开会决定,中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分,发布为 ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为 Harmony(和谐)。会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。
2009 年 12 月,ECMAScript 5.0 版正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。TC39 委员会的总体考虑是,ES5 与 ES3 基本保持兼容,较大的语法修正和新功能加入,将由 JavaScript.next 完成。当时,JavaScript.next 指的是 ES6,第六版发布以后,就指 ES7。TC39 的判断是,ES5 会在 2013 年的年中成为 JavaScript 开发的主流标准,并在此后五年中一直保持这个位置。
2011 年 6 月,ECMAScript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。
2013 年 3 月,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript 7。
2013 年 12 月,ECMAScript 6 草案发布。然后是 12 个月的讨论期,听取各方反馈。
2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。从 2000 年算起,这时已经过去了 15 年。
但是,因为这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布 6.0 版,过一段时间再发 6.1 版,然后是 6.2 版、6.3 版等等。 但是,标准的制定者不想这样做。他们想让标准的升级成为常规流程:任何人在任何时候,都可以向标准委员会提交新语法的提案,然后标准委员会每个月开一次会,评估这些提案是否可以接受,需要哪些改进。如果经过多次会议以后,一个提案足够成熟了,就可以正式进入标准了。这就是说,标准的版本升级成为了一个不断滚动的流程,每个月都会有变动。
标准委员会最终决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了。
ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。
因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。
2. ECMAScript版本特性
ES3
因为 ECMAScript
要晚于 JavaScript
, 因此在前面几个版本, 主要是对 JavaScript
进行标准化, 我找了下面的一个表格, 可以看到在 ES3
版本时标准的语法内容, 基本上当时的 JavaScript
已经全部支持了.
特性 | JavaScript 1.2 | JScript 3.0 | ECMA-262 第 3 版 |
---|---|---|---|
do 语句 | ✓ | ✓ | ✓ |
break/continue 到标签 | ✓ | ✓ | ✓ |
switch 语句 | ✓ | ✓ | ✓ |
嵌套函数 | ✓ | ✓ | ✓ |
函数表达式 | ✓ | ✓ | ✓ |
对象字面量 | ✓ | ✓ | ✓ |
数组字面量 | ✓ | ✓ | ✓ |
=== 和 !== | ✓ | ✓ | |
正则表达式字面量 | ✓ | ✓ | ✓ |
delete 运算符 | ✓ | ✓ | ✓ |
所有对象上的 proto 伪属性 | ✓ | ||
数组方法 concat, slice | ✓ | ✓ | ✓ |
数组方法 push, pop, shift, splice, unshift | ✓ | ✓ | |
带有继承元素的稀疏数组 | ✓ | ✓ | |
使用正则表达式的字符串方法 fromCharCode, match, replace, search, substr, split | ✓ | ✓ | ✓ |
字符串方法 charCodeAt | ✓ | ✓ | |
正则表达式方法 compile, exec, test | ✓ | ✓ | ✓ |
正则表达式属性 $1…$9, input | ✓ | ✓ | |
正则表达式全局属性 lastMatch, lastParen, leftContext, rightContext | ✓ | ||
带有本地声明属性的 arguments 对象 | ✓ | ✓ | |
arguments.callee | ✓ | ✓ | |
arguments.caller | ✓ | ✓ | |
watch/unwatch 函数 | ✓ | ||
import/export 语句与脚本签名 | ✓ | ||
条件编译 | ✓ | ||
debugger 关键字 | ✓ |
ES5
ES5
进一步完善了 ECMAScript
, 主要特性主要有,
- 提供了严格模式,
- 增强内置对象的方法
- 属性 Getter 和 Setter
如数组的方法
- Array.isArray()
- Array.forEach()
- Array.map()
- Array.filter()
- Array.reduce()
- Array.reduceRight()
- Array.every()
- Array.some()
- Array.indexOf()
- Array.lastIndexOf()
字符串的方法
- String.trim()
日期对象的方法
- Date.now()
还新增了JSON
的两个常用方法
- JSON.parse()
- JSON.stringify()
同时也扩展了Object的很多方法, 如
// 添加或更改对象属性
Object.defineProperty(object, property, descriptor)
// 添加或更改多个对象属性
Object.defineProperties(object, descriptors)
// 访问属性
Object.getOwnPropertyDescriptor(object, property)
// 将所有属性作为数组返回
Object.getOwnPropertyNames(object)
// 将可枚举属性作为数组返回
Object.keys(object)
// 访问原型
Object.getPrototypeOf(object)
// 防止向对象添加属性
Object.preventExtensions(object)
// 如果可以将属性添加到对象,则返回 true
Object.isExtensible(object)
// 防止更改对象属性(而不是值)
Object.seal(object)
// 如果对象被密封,则返回 true
Object.isSealed(object)
// 防止对对象进行任何更改
Object.freeze(object)
// 如果对象被冻结,则返回 true
Object.isFrozen(object)
ES6(ES2015)
ES6
大家应该是非常熟悉的, 因为经过十几年的制定, 积累了大量的内容, 可以说是划时代的版本.ES6
的新特性非常的多, 我们这里简单的罗列一下, 具体的可以去看阮一峰
老师写的 ECMAScript6入门
(因为ES6
是一个大版本, 所以后续的 ES2016
,ES2017
这类小版本也是归纳在ES6
中的, 我们这里仅列举ES2015
的新特性),在参考链接里面有.
1.let
与 const
2.解构赋值
3.字符串扩展(迭代遍历, 模板字符串
4.字符串新增方法
fromCodePoint()
,raw()
,codePointAt()
,normalize()
,includes(),startsWith(),endsWith()
,repeat()
5.正则表达式(各种修饰符)
6.数值扩展(isFinite()
,isNaN()
,isInteger()
, Math
对象的方法扩展)
7.函数扩展(参数默认值, rest(...)
参数, 箭头函数)
8.数组扩展
扩展运算符...
, Array.from()
, Array.of``Array.copyWithin()
,find()
,findIndex()
,findLast()
,findLastIndex()
, fill()
,entries(), keys(), values()
, includes()
,flat(), flatMap()
)
9对象扩展
10 对象新增方法
Object.is()
(和===
有区别)Object.assign()
11.Symbol
12.Set(WeakSet)
与 Map(WeakMap)
13.Proxy
14.Reflect
15.Promise
16.Iterator
与for...of
17.Generator
18.Class
19.Module
(模块化)
ES2016
1.Array.prototype.includes()
2.幂运算 a ** b = Math.pow(a, b);
ES2017
1.String.prototype.padStart
, String.prototype.padEnd
("1").padStart(2, "0"); // "01"
2.async/await
Promise处理异步的语法糖, 可以像写同步代码一样写异步, 规避回调地狱.
3.Object.entries()
返回对象自身可枚举属性的键值对数组4. Object.values()
返回对象自身的所有属性值, 不包括继承的值5. Object.getOwnPropertyDescriptors
返回对象所有自身属性的描述符.6函数参数列表结尾允许逗号
7.SharedArrayBuffer
共享的内存空间, 区别于ArrayBuffer
是不能被转移(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)8. Atomics
提供静态方法对ShaderArrayBuffer
进行原子操作.
ES2018
1.对象的 rest/spread运算符
在ES2015中, 为数组引入了 rest/spread
运算符, 我们可以用以简单的替换到 concat
或slice
方法.
在ES2018中, 我们同样的操作可以用在对象上了, 如
const a = {b: 1, c: 2};
const d = {...a}; // Object.assign({}, a);
const {b, ...e} = a; // e: {c: 2}
2.异步迭代Symbol.asyncIterator
异步迭代器与传统迭代器的不同点在于,它不返回 {value,done} 的形式的普通对象,而是返回一个完成(fulfill) {value,done} 的promise. 同样的, 异步迭代的遍历, 不能直接用for..of, 而是 for…await…of
for await (const x of collection) {
console.log(x);
}
3. Promise.prototype.finally
4.RegExp新特性, /s(dotAll), 可命名捕获组, Lookbehind断言与Unicode属性转义
其中比较有意思的是可命名捕获组, 正常我们在用正则匹配需要的字符串时, 一般需要用exec返回的match数组, 比如:
const
reDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/,
match = reDate.exec('2018-04-30'),
year = match[1], // 2018
month = match[2], // 04
day = match[3]; // 30
而可命名捕获组则可以用 ?<name>
来给捕获的结果指定name
, 然后我们直接通过name
就能拿到我们要的值了.
const
reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,
match = reDate.exec('2018-04-30'),
year = match.groups.year, // 2018
month = match.groups.month, // 04
day = match.groups.day; // 30
同样的, 命名捕获也可以用在String.prototype.replace()
中.
const
reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,
d = '2018-04-30',
usDate = d.replace(reDate, '$<month>-$<day>-$<year>');
ES2019
1. try..catch可选参数e
之前我们写 try...catch
都要加上异常变量 e
, 如:
try {
...
} catch(e) {
...
}
现在, 这个e
是可选的了, 比如
try {
...
} catch {
...
}
2. Symbol.prototype.description
用以修饰 Symbol
const s = Symbol('foo');
console.log(s.description); // foo
const s1 = Symbol();
console.log(s1.description); // undefined
3.Function.prototype.toString
4.Object.fromEntries
和 Object.entries
刚好相反. 如:
const obj = {a: 1, b: 2};
const entries = Object.entries(obj); // [["a", 1], ["b", 2]];
const obj2 = Object.fromEntries(entries); // obj.
5. Array.prototype.flat 与 Array.prototype.flatMap
flat
方法用于把数组拍平, 相当于把_.flatten, _.flattenDeep, _.flattenDepth
三个方法合成一个方法, 如:
// 不传参数的话, 默认拍平一层
[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]]]
// 也可以指定拍平的层数
[1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5]
// 直接拍到最底层可以传 Infinity
[1, 2, [3, [4, 5, [6, 7]]]].flat(Infinity) // [1, 2, 3, 4, 5, 6, 7]
flatMap
则是 flat
+ map
, 如
let arr = [1, 2, 3, 4];
arr.flatMap(x => [x*2]) // [2, 4, 6, 8]
6.String.prototype.timStart 与 String.prototype.trimEnd
trim
用于删除字符串两端的空字符串, 同样的, trimStart
则是只删除前面, trimEnd
只删除后面.
ES2020
1. String.prototype.matchAll
返回一个捕获分组的迭代器, 与 match
相比的话, 分组结果更明确了.
var regexp = /t(e)(st(\d?))/g;
var str = 'test1test2';
str.match(regexp);
// Array ['test1', 'test2']
let array = [...str.matchAll(regexp)];
array[0];
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
array[1];
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]
2.动态import
import('./a.js').then(module => {
console.log(module.hello()); // Hello World !
})
3. BigInt
之前在JS中, 最大的安全整数时 2^53 - 1
而使用BigInt.
4. Promise.allSettled.
和 Promise.all
相近, 区别于 Promise.all
会短路, 即只要有一个Promise
对象进入rejected
状态, Promise.all
就会提前结束.
但是Promise.allSettled
则不会, 会等所有的Promise
都结束了再结束
5.globalThis
浏览器环境下全局对象是window
, node
环境下全局对象则是 global
, 在webworker
中, 全局对象又是self….
所以, 我们写的代码, 如果想要支持跑在更多的环境中, 需要手动处理一下全局对象的获取, 比如 underscore
里面获取全局对象的判断
var root = (typeof self == 'object' && self.self == self && self) ||
(typeof global == 'object' && global.global == global && global) ||
this;
6.链判断运算符 ?.
var c = a?.b; // a == null ? undefined : a.b;
d.e?.(); // d.e && d.e()
7. null判断运算符 ??
null ?? 1; // 1
undefined ?? 1; // 1;
// 类似于 a == null ? 1 : a;
8. import.meta
在ESModule
里面, 可以使用 import.meta
查看当前模块的信息, 如
import.meta.url; // xxx.js
.
在vite
中, 则是直接把全局参数注入到 import.meta
中使用
ES2021
1.String.prototype.replaceAll
之前我们想对字符串做批量替换, 得用正则表达式 /xxx/g
而写过java
的同学都知道, 其中有一个string.replaceAll
可以批量替换, 现在在 js
里面也可以这么写了
2. Promise.any
传入一组可迭代的Promise
集合, 返回一个新的 Promise
, 并且当集合中出现一个进入fullfilled
的Promise出现时, 返回的 Promise
也会被标记为fullfilled
, 如果集合中没有出现fullfilled
的Promise
, 那么则返回rejected
.
类似于 Array.prototype.any
, 即出现第一个满足条件的就终止, 否则则失败.
我们需要关注一下其与 Promise.all
和 Promise.race
的区别Promise.all
返回的是一组Promise
的状态, Promise.any
或Promise.race
只返回一个.
而与Promise.race
的区别在于, 后者返回第一个完成或者失败的Promise
(最快结束). 而Promise.any
则会尝试寻找第一个完成的Promise
, 都没有才返回rejected
.
3. WeakRefs
弱引用, 不会影响垃圾回收, 和 WeakMap
差不多, 区别在于可以直接引用一个对象, 不需要什么key
了. 使用方法
var foo = () => {console.log('hi')};
var weakFoo = new WeakRef(foo);
// 使用 deref()获取原始的对象.
console.log(weakFoo.deref()) // () => {console.log('hi')}
4.逻辑运算符 ||=, &&=, ??=
// 或赋值运算符
x ||= y
// 等同于
x || (x = y)
// 与赋值运算符
x &&= y
// 等同于
x && (x = y)
// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)
5.数值分割符
1000000 == 1_000_000; // 更好阅读了
ES2022
1. 类的字段定义, 私有属性
class a {
b = 1;
#c = 2;
#d() {
console.log(this.#c);
}
}
2. RegExp Match Indices
给正则表达式加一个/d
的修饰符, 这样使用exec()
方法后, 返回的结果会多一个 indices属性, 表示匹配字符串在原始字符串中的索引.
3. Top-level await
以前我们写 async...await
, await
必须要在async
内部.
最新的提案允许我们直接使用 await
4. in 方法可以判断私有属性
class C {
#brand;
#method() {}
get #getter() {}
static isC(obj) {
return #brand in obj && #method in obj && #getter in obj;
}
}
5. at()
新增的取值方法, 可以用于 Array
, String
, TypedArray
和直接用[]
区别在于, 可以用负数取倒数第n
个元素
6. Object.hasOwn();
改良版本的Object.prototype.hasOwnProperty()
// object继承自对象{ foo: 'foo' }
const object = Object.create({ foo: 'foo' })
// 再给本身添加属性bar
object.bar = 'bar'
object.hasOwnProperty('foo') // false
object.hasOwnProperty('bar') // true
Object.hasOwn(object, 'foo') // false
Object.hasOwn(object, 'bar') // true
值得注意的是, 如果直接Object.create(null)
得到的对象是没有继承Object.prototype
, 因此不能直接用hasOwnProperty
, 使用Object.hasOwn
可以规避这个问题.
7. 静态作用域
给一个静态代码块, 可以直接对执行对静态变量做修改, Java
里面已经有了.
class A {
static b = 1;
static {
if (c()) {
this.b = 2;
}
}
}
Babel
上面我们了解到了 ECMAScript
以及每个版本的新特性, 而我们要想在实际编码中, 使用这些新的特性, 就不得不考虑浏览器兼容性的问题, 毕竟我们用了2020的新特性, 但是有可能跑在2019年发布版本的浏览器上.
为此, 我们需要把高版本的语法, 转换成低版本的, 从而保证可以在不同版本的浏览器上正常运行, 这个转换就需要用到Babel.
一般来说, 我们只需要兼容到 ES5
版本(2009年发布)即可, 更低的版本理论上也能兼容, 但是那种古董级的浏览器, 使用占比已经很小很小了, 我刚来公司的时候, 图表还要兼容IE6, 现在基本只要求到IE11就可以了.
虽然现在很多集成的cli已经帮我们处理好了 babel
的配置, 如 react-scripts
, vite
等, 但是作为行业内最优秀的语法转换库, 我们还是有必要了解一下其使用方式以及背后的工作原理.
Babel
的工作内容主要分成三个部分, 解析, 转换, 生成.
其中, Babel
自身是不处理任何转换操作的, 只负责解析JS的源码, 生成ast
的语法树, 然后把转换的工作, 交给不同的插件去做, 每个插件只做一个语法特性的转换, 所以我们要把 诸如es6
的全部特性都转成es5
的话,就得安装一堆转换的插件, 如duchamp
中 @babel
下的转换插件就有好几十个.
所以我们在使用Babel的时候, 主要工作就是根据需要安装配置插件.
当然, 一次性安装这么多转换插件, 第一是记不住, 第二一个一个安装还是很麻烦的。
所以 Babel
官方也给我们提供了常用的语言标准转换所需要的插件合集, 即
babel-preset
,
里面包含了我们常用的几种语言转换规则,如 env
,react
,typescript
(没错,虽然tsc
已经支持直接把ts
转成指定版本的js
,但是babel
提供的转换规则给细致,而且自定义程度更高,所以一些开发者习惯于用babel
编译ts
,而typescript
本身只做类型检查)
上面我们说的几个都是 最新的 babel 7.x
版本内置的, 早些版本的 babel-preset
还有stage-x
和es201x
,
前者用以表示当前还在 ECMAScript
提案中或者已经纳入标准但是还没发布的新语法,其中还细分有:
- Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
- Stage 1 - 提案: 初步尝试。
- Stage 2 - 初稿: 完成初步规范。
- Stage 3 - 候选: 完成规范和浏览器初步实现。
- Stage 4 - 完成: 将被添加到下一年度发布。
后者则是直接指定具体的年份版本
不过这两个在最新的 Babel7
都被删掉了, 因为env
已经足够强大,而且配置更灵活
现在我们只需要一个@babel/preset-env
即可覆盖到最新的ECMAScript
标准
关于 @babel/preset-env
内置的插件以及作用, 可以参考:
当然除了 babel-preset
之外, babel
还提供了其他的一些转换工具如
babel-cli
最新版的包叫做 @babel/cli
,就是命令行工具,安装了这个就可以直接在命令行里面使用 babel
对指定的文件进行转换。
babel-node
之前是集成在 babel-cli
中, babel7
独立出来, 叫做@babel/node
用以在低版本node环境中,使用高版本的 ECMAScript
,我们感觉用不到, 因为我们都是用的比较新的node
天然支持新特性语法
babel-register
可以改写 nodejs
中的require
执行, 并挂载hooks
,从而对使用require
加载的文件, 使用babel
转码
babel-polyfill
babel
默认只会对新语法进行转换,比如 let,const
,箭头函数
,class
等
而对于一些新的API
如Proxy
,Symbol
,Set
等,以及各种对象新增的方法Object.assign
则是不转换的.
为此, 我们需要使用 babel-polyfill
对这些新的API
进行转换.babel-polyfill
也有缺点:
- 使用 babel-polyfill 会导致打出来的包非常大,因为 babel-polyfill 是一个整体,把所有方法都加到原型链上。比如我们只使用了 Array.from,但它把 Object.defineProperty 也给加上了,这就是一种浪费了。这个问题可以通过单独使用 core-js 的某个类库来解决,core-js 都是分开的。
- babel-polyfill 会污染全局变量,给很多类的原型链上都作了修改,如果我们开发的也是一个类库供其他开发者使用,这种情况就会变得非常不可控。
所以在 Babel7.4
版本之后,这个包已经被标为deprecated
官方推荐我们直接使用core-js/stable
来代替它.
babel-run-time与babel-plugin-transform-runtime
babel-run-time是包含各种语法转换的工具库
比如
class Circle {}
经过转换后变成了
function _classCallCheck(instance, Constructor) {
//...
}
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
但是每个文件里面的class都换转换成_classCallCheck
这样就形成了重复代码,为此引入了@babel/plugin-transform-runtime
,它会把这些方法替换成babel-rumtime
中的工具方法引用, 如:
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() {
_classCallCheck(this, Circle);
};