背景
在文章前我想问下下读者是否知道:ECMA、 TC39、ECMAScript 这些是什么,他们有什么关联?JS新出的API为什么总是从他们文章里面了解。作者曾经也不清楚里面的关系,所以为了搞懂作者进行大量的翻阅去了解里面的关系,并写下这个文章做总结。
关于文章的目的是让大家知道现时间段可以尝鲜的提案,了解JS这门语言最新的动向。另外还有一个目的就是:授人以鱼不如授人以渔, 让大家知道去哪里查阅最新提案,这个流程的发起是怎么样的。
后续的阅读作者将从:JS的发展史 ➡️ TC39成立 ➡️ JavaScript标准化流程➡️ 新提案尝鲜 带大家了解JS这门语言规范维护和它的更新流程,那么开始吧。
JS故事
1992年底,美国国家超级电脑应用中心(NCSA)开始开发一个独立的浏览器,叫做Mosaic。这是人类历史上第一个浏览器,从此网页可以在图形界面的窗口浏览。
1994年10月,NCSA的一个主要程序员Marc Andreessen联合风险投资家Jim Clark,成立了Mosaic通信公司(Mosaic Communications),不久后改名为Netscape(网景)。这家公司的方向,就是在Mosaic的基础上,开发面向普通用户的新一代的浏览器Netscape Navigator。
1994年12月,Navigator发布了1.0版,市场份额一举超过90%
1995年,Netscape公司(网景)雇佣了程序员Brendan Eich开发这种网页脚本语言。Brendan Eich有很强的函数式编程背景,希望以Scheme语言(函数式语言鼻祖LISP语言的一种方言)为蓝本,实现这种新语言(JavaScript)。
1995年 Brendan Eich花费十天就设计完成了这种语言的第一版
1995年12月4日,Netscape 公司与 Sun 公司联合发布了 JavaScript 语言。当时的意图是将 JavaScript 作为 Java 的补充,用来操作网页。 1996年3月,Navigator 2.0 浏览器正式内置了 JavaScript 脚本语言。
1996年8月,微软模仿JavaScript开发了一种相近的语言,取名为JScript(JavaScript是Netscape的注册商标,微软不能用),首先内置于IE 3.0。Netscape公司面临丧失浏览器脚本语言的主导权的局面,这里我的理解是微软把自己的浏览器和自己的操作系统关联到一起把市场份额占有率提高(本身自己浏览器也好用的前提吧),影响了Netscape公司主导权原因。
眼看主导权要没,和各浏览器厂商独立实现JavaScript,导致了不同浏览器之间的不兼容性,Netscape公司将JavaScript提交给国际标准化组织ECMA(European Computer Manufacturers Association),希望JavaScript能够成为国际标准,以此抵抗微软。
ECMA(国际标准化组织)
ECMA(European Computer Manufacturers Association)作为一个国际标准化组织,通过制定和发布各种技术标准,推动信息和通信技术的发展和普及。其快速响应和高效的标准制定流程,使得ECMA在全球范围内具有重要影响力。特别是通过其技术委员会(如TC39),ECMA在编程语言、数据通信、多媒体和信息存储等领域,发布了许多关键的国际标准。
TC39委员会
1996年11月,ECMA创建了第39号技术委员会(TC39)。主要参与制定和维护ECMAScript标准,TC39成员是由主要浏览器厂商(如谷歌、微软、苹果、Mozilla)派代表参加,来确保其浏览器的JavaScript引擎符合最新标准。另外也有各大技术公司派出代表,提供不同的视角和需求。和其他技术专家等等。总而言之就是制定 ECMAScript(JavaScript) 标准的委员会。最终各个浏览器厂商根据这个组织颁布的规范去实现
ECMAScript的提案流程:提案流程由 TC39(Technical Committee 39)管理,确保每个新特性都经过严格的讨论、实验和验证,以保证其质量和稳定性。整个流程分为六个主要阶段:
- 阶段 0(Strawman):提出初步想法,任何人都可以提出新的想法,并在 TC39 会议上进行讨论。这阶段的想法还没有正式列入提案流程。
- 阶段 1(Proposal):获得委员会的同意,将想法正式列入提案流程。提案需要有一位 TC39 委员会成员担任提案发起者(Champion),并提供高层次描述、动机、示例代码以及初步的语法和语义描述。提案需要通过委员会的同意,才能进入阶段 2。
- 阶段 2(Draft):制定正式的规范草案。提案者需要提供详细的规范草案,包括语法、语义和边界情况,并在实际项目中进行实验和反馈。提案需要通过委员会的同意,才能进入阶段 3。
- 阶段 2.7: (Testing and Validation):提案的规范完成,并通过评审和编辑的批准。需要通过测试和原型验证来确保规范的准确性和实用性。
- ** 阶段 3(Candidate)**:确认提案的规范已经足够完善,准备进行广泛的实现和测试。提供完整的规范草案,需要两个独立的实现,并通过相关的测试套件。提案需要通过委员会的同意,才能进入阶段 4。
- 阶段 4(Finished):提案已准备好纳入正式的 ECMAScript 标准。提案者需要确保规范完全稳定,并且已经在实际项目中验证过,至少两个独立实现已经通过所有测试。通过委员会的最终批准后,提案将被纳入下一个 ECMAScript 标准版。
TC39 遵循的原则是:分阶段加入不同的语言特性。一旦提案成熟,TC39 会根据提案中的变动来更新规范(上方所提的规范)。那它有几个阶段每个阶段又代表的是什么呢。
ECMAScript
JavaScript 和 ECMAScript的区别?他们本质上是一个东西,因为Sun(现在的Oracle)公司持有着“Java”和“JavaScript”的商标。这就让微软不得不把自己的JavaScript方言称之为“JScript”。然后,在这门语言被标准化的时候,就必须使用一个与二者都不同的名字。“ECMAScript”就这样诞生了,这个名字的来由是因为执行标准化的组织是Ecma国际,所以叫ECMAScript
ECMAScript遵循了不同的标准规范,每个规范所负责的内容都不一样。浏览器厂商都会遵循这些规范去同步实现相对应的功能与API。 下面是标准规范和规范所负责的内容介绍:
ECMA-262
定义了ECMAScript语言的核心语法、类型、对象模型和标准库。这是JavaScript的基础规范。ECMA-262经历了多次修订,最新版本包括许多现代编程特性。
主要内容:
- 语法:定义了ECMAScript的语法,包括变量声明、控制结构、函数等。
- 类型:介绍了ECMAScript的基本类型(如Number、String、Boolean、Object、Undefined、Null、Symbol、BigInt)。
- 对象模型:详细描述了ECMAScript对象的属性、方法和原型链机制。
- 标准库:定义了内置对象和函数(如Math、Date、RegExp、JSON、Promise等)。
- 运行时环境:描述了ECMAScript的执行环境,包括作用域、上下文、事件循环等。
- 错误处理:定义了异常和错误处理机制。
ECMA-402
定义了ECMAScript国际化API,用于处理不同语言和文化的日期、时间、数字和货币格式化
主要内容:
- 日期和时间格式化:
Intl.DateTimeFormat用于根据地区设置格式化日期和时间。 - 数字格式化:
Intl.NumberFormat用于根据地区设置格式化数字,包括货币和百分比格式。 - 字符串比较:
Intl.Collator用于根据地区设置比较字符串,实现本地化排序。 - 国际化支持:支持不同语言和地区的本地化需求,提供多语言支持。
ECMA-404, JSON
定义了JSON(JavaScript Object Notation)数据交换格式`
主要内容:
- 语法:定义了JSON的语法,包括对象(键值对集合)和数组(有序值列表)。
- 数据类型:支持字符串、数字、布尔值、数组、对象和null。
- 字符编码:JSON使用UTF-8编码。
- 互操作性:确保JSON格式在不同系统之间能够互操作,广泛应用于Web开发中客户端与服务器的数据交换。
ECMA-414
这个标准描述了ECMAScript规范的套件,集合了不同版本和相关标准。它是一个汇总性文档,整合了与ECMAScript相关的各个标准
主要内容:
- 汇总和整合:集合了与ECMAScript相关的各个标准,如ECMA-262、ECMA-402和ECMA-404。
- 版本管理:提供不同版本的规范文档和更新历史。
- 参考资源:为开发者和实现者提供一个综合的参考,便于获取所有相关的ECMAScript规范。
新提案的尝鲜
在尝鲜新提案前,我们已经通过上方的的铺垫知道了ECMAScript的规范的定制和整个流程。那既然作为国际标准化组织当然有地方找到它们,我们可以点击此处来访问TC39官网,在官网有展示现阶段Stage3的提案,我们也可以通过官网直接跳转对应提案的介绍。来了解此提案的内容和解决的问题。还可以点击此处查看讨论区看看他们都在讨论那些有意思的内容, 包括点击这里也能了解TC39这个组织的工作方式(比如:如何加入TC39、规范规则、术语解释、如何参加会议、如何召开在线会议等等....),在官网底下有很多可以关注他们的平台 Github、Twitter Matrix
我们查阅新提案的方式可以直接通过TC39官网查看Stage3列表的提案,这些提案属于等待发布了。如果想查看其他阶段的提案我们也可以直接通过官网最底下有个查看个个阶段的提案。
挑几个我觉得有意思的,后续如果想查阅更多直接在官网或github查看。
Temporal
介绍:Temporal 是一个关于标准化日期时间新提案,存在于浏览器顶层全局的一个API。该提案的缘由是来自这个文章,简单概括因为 JavaScript 最初关于日期的实现是照搬的 Java 方案,但由于各种限制和问题,Java 早在 1997 年就实现 Calendar 做了功能改进,而 JavaScript 时至今日用的还是老旧方案,关于老的方案存在以下一些问题:
1. 不支持除用户本地时间和 UTC 之外的时区
1. 解析器行为非常不可靠,无法使用
1. 日期对象是可变的
1. DST 行为不可预测
1. 计算 API 难以操作
1. 不支持非公历
当然上方的有些问题是可以被修复的,但是2和3对于现有的规范和修复所带来的破坏性更新导致无法被修复。比如:
function addOneWeek(myDate) {
myDate.setDate(myDate.getDate() + 7);
return myDate;
}
var today = new Date();
var oneWeekFromNow = addOneWeek(today);
console.log(`today is ${today.toLocaleString()}, and one week from today will be ${oneWeekFromNow.toLocaleString()}`);
//today is 4/16/2017, 10:58:10 AM, and one week from today will be 4/16/2017, 10:58:10 AM
在工具函数的封装上,使用现有的日期设置星期会影响着外部传入的Date,也就是问题点2。如果我们期望日期对象是不可变,返回的是一个新的日期对象
//today is 4/16/2017, 10:58:10 AM, and one week from today will be 4/16/2017, 10:58:10 AM
进行此更改的话,大量依赖于日期可变的代码(包括整个 Moment.js 库)将被破坏。这也是为什么有新提案的原因。了解了新提案的缘由我们将开始简单的学习Temporal
const date = Temporal.PlainDate.from({
year: 2024,
month: 7,
day: 27,
});
date.year; // => 2024
date.inLeapYear; // => false
date.toString(); // => '2024-07-27'
date.dayOfYear // 209 今年的第几天
date.weekOfYear // 30 第几周
date.dayOfWeek // 6 周六 最大到 7(日)
date.daysInMonth // 31 当月总天数
date.withCalendar("chinese").day // 22 返回中国的农历
const yearMonth = Temporal.PlainYearMonth.from({ year: 2024, month: 7 }); // => 2024-07
yearMonth.daysInMonth; // => 31 当月有几天
yearMonth.daysInYear; // => 366 今年一共有几天
yearMonth.dayOfYear
// 时间对比
const one = Temporal.PlainYearMonth.from('2006-08');
const two = Temporal.PlainYearMonth.from('2015-07');
const three = Temporal.PlainYearMonth.from('1930-02');
const sorted = [one, two, three].sort(Temporal.PlainYearMonth.compare);
const sorted.join(' '); // => '1930-02 2006-08 2015-07'
// 对比相差时间
const date1 = Temporal.PlainDate.from('2022-04-01');
const date2 = Temporal.PlainDate.from('2022-04-07');
const diffDays = date1.until(date2).days; // 6
Decorators
介绍:Decorators原生装饰器,如果熟悉Babel、TypeScript、Nest.js的同学应该有了解,装饰器为类和类成员提供了一种强大的元编程能力,可以帮助开发者在不改变原有代码逻辑的情况下,添加额外的功能和元数据。
装饰器参数说明:
type Decorator = (value: Input, context: {
kind: string; // 当前被装饰的类型:class、method、getter、setter、field、accessor
name: string | symbol;
access: {
get?(): unknown;
set?(value: unknown): void;
};
private?: boolean;
static?: boolean;
addInitializer(initializer: () => void): void;
}) => Output | void;
function logged(value, { kind, name }) {
if (kind === "method") {
return function (...args) {
console.log(`starting ${name} with arguments ${args.join(", ")}`);
const ret = value.call(this, ...args);
console.log(`ending ${name}`);
return ret;
};
}
}
class C {
@logged
m(arg) {}
}
new C().m(1);
// starting m with arguments 1
// ending m
PipelineOperator
管道操作符, |> 提供了一种流畅的语法,将表达式的结果传递给下一个函数。这样提高了代码的可读性,符合人们从左到右或从上到下的阅读代码的习惯,同时还能减少中间变量的存在。我们通过官方的案例来对比下使用管道的代码。
// Status quo
var minLoc = Object.keys( grunt.config( "uglify.all.files" ) )[ 0 ];
// With pipes
var minLoc = grunt.config('uglify.all.files') |> Object.keys(%)[0];
// Status quo
const json = await npmFetch.json(npa(pkgs[0]).escapedName, opts);
// With pipes
const json = pkgs[0] |> npa(%).escapedName |> await npmFetch.json(%, opts);
从官方提供的示例代码中我们会很容易的爱上管道操作符,因为他在可读性和阅读代码顺序上非常符合人的思维。好用,会用,爱用
Array.fromAsync
这个特性在Chrome浏览器已经支持了,它和Array.form类似但是它是处理异步迭代器。
function getData(type){
return new Promise(resolve => setTimeout(()=> resolve({type, data: type}), 500 * type))
}
const types = [1, 2, 3]
Array.fromAsync(types.map(getData)).then(res => {
console.log(res) // [{"type": 1, "data": 1}, {"type": 2,"data": 2}, {"type": 3,"data": 3}]
})
Math.sumPrecise
用于在 JavaScript 中精确地求和。与传统的 Array.prototype.reduce 方法相比,Math.sumPrecise 使用更高级的算法来提高浮点数相加的准确性。
let values = [1e20, 0.1, -1e20];
values.reduce((a, b) => a + b, 0); // 0
Math.sumPrecise(values); // 0.1
Throw Expressions
关于throw表达式的使用,用于在表达式上下文中抛出异常。可以搭配另外一个新提案进行判断是否是一个error, Error.isError
function save(filename = throw new TypeError("Argument required")) {}
lint(ast, {
with: () => throw new Error("avoid using 'with' statements.")
});
function getEncoder(encoding) {
const encoder = encoding === "utf8" ? new UTF8Encoder()
: encoding === "utf16le" ? new UTF16Encoder(false)
: encoding === "utf16be" ? new UTF16Encoder(true)
: throw new Error("Unsupported encoding");
}
彩蛋
通过上面的学习,我们把关于ECMAScript(Javascript)的更新和提案流程都弄清楚。包括最新的提案体验和如何持续跟进最新状态的方式也都走了一遍。其实我们上面所了解的只是关于前端的冰山一角,关于整个前端规范我们还有很多可以学习的方向,前端的如何进阶我觉得我们也可以在这些方面下手,下面就跟随彩蛋的章节我们继续扩大范围去了解&学习吧。