本系列文章经过重新编排和扩充,已出书为ECMAScript关键30天。原始文章因当时准备时程紧迫,多少有些许错误。为了避免造成读者的困扰,以及配合书籍的内容规划,将会陆续更新本系列文章。
本篇文章在 2021/11/1 已更新,刊载在我的Blog
ECMAScript 原本是不定期地释出版本,但因应提案的踊跃和开发需求的迫切,所以从 ES2015 後就改为一年一修。也就是说每年都会有新的语法标准出现,让开发者可以使用更简洁弹性的语法撰写,或是实现更强大的功能。
接下来整理预计会在 ES2022 释出的提案,以简单的说明加上清楚的程序范例,快速了解起手式?
一个想法出来到纳入提案,接着成为修订标准,通常会经历以下 5 个阶段-
Stage | 说明 |
---|---|
0 | 由 TC-39 的成员或其他人提出後,委员会没有否决的讨论或想法 |
1 | 正式成为提案,需有具体语法和描述,部分会有Polyfill的实现 |
2 | 可能有相关的运作环境或编译器提供实验性的功能实现 |
3 | 成为候选提案,部分运作环境或编译器提供原生支援 |
4 | 通过至少两个验收测试,等待下版释出时成为修订内容 |
今天要讲的语法,都是已经进到 finished,也就是 Stage 4 阶段的提案。有兴趣的话,可以前往 TC-39 的 GitHub 看相关整理。有趣的是,在这里不仅可以看到语法的开发动机和具体描述,也能看到在提案推进的过程中,开发者与委员们的讨论纪录,可以更进一步地了解标准推出的始末。
先简单复习一下。正规表达式主要有以下三种组成-
其中旗标的部分,一个 RegExp 物件会针对旗标提供实体属性来查询。透过取得属性值,就可以知道这个正规表达式有没有设定对应的旗标。
Stage | 对应属性 | 说明 |
---|---|---|
d | hasIndices | 回传每个匹配内容的起始&下次要开始检索的索引阵列([startIndex, endIndex+1] ) |
有加上 d
旗标的正规表达式,在执行匹配相关的操作,例如 exec
时,可以多回传一个 indices
属性。这个属性会包含匹配内容的起始索引,以及结束索引加 1,也就是视为下次要开始检索的索引。
目前最新版本的 Chrome 还有Node.js中可以运作,有兴趣的话可以尝试看看。
const myRegexp = /\w*.o\w+/dgi; // 找出中间有 o 的单字
const target = 'Born to make history';
let currResult;
while ((currResult = myRegexp.exec(target)) !== null) {
console.log('这次的符合内容: ', currResult[0]);
console.log('Matched Indices: ', currResult.indices[0]);
console.log('---');
}
// 这次的符合内容: Born
// Matched Indices: (2) [0, 4]
// ---
// 这次的符合内容: history
// Matched Indices: (2) [13, 20]
// ---
常见的这类物件有字串跟阵列。那麽在 ES2022 推出了什麽相关语法呢?
indexable.at(index)
用途跟使用字面值取得元素的方式类似,透过索引的传入来获得元素。不过索引值在负整数跟浮点数时,跟字面值的回传结果会不一样。可以看一吓得比较表-
取得元素的方式 | 负整数 | 浮点数 |
---|---|---|
indexable[index] |
undefined |
undefined |
indexable.at(index) |
後面从 -1 开始算 | 无条件舍去後,再由正负数决定查找方向 |
目前最新版本的 Chrome 可以运作,有兴趣的话可以尝试看看。
const myArray = [0, 1, 2, 3];
console.log(myArray[-2], myArray.at(-2)); // undefined 2
console.log(myArray[1.6], myArray.at(1.6)); // undefined 1
console.log(myArray[-3.2], myArray.at(-3.2)); // undefined 1
Object.hasOwn(target, propName)
用来检查目标物件有没有属性。那跟目前习惯使用的 hasOwnProperty
有什麽不一样呢?
假设以 Object.create(null)
建立了空物件,然後新增一些属性後,我们想检查这个物件有没有特定属性。可是物件的原型指向了 null
,所以就无法使用实体方法 hasOwnProperty
,除非要直接呼叫 Object.prototype.hasOwnProperty
加上 call
方法来达到这个目的。不过这样的写法太冗长了。因此 ES2022 就在 Object
底下新增这个静态方法,成为语法糖。
目前最新版本的 Chrome 可以运作,有兴趣的话可以尝试看看。
// before
const hasNameProp = Object.prototype.hasOwnProperty.call(myObject, 'name');
// after
const hasNameProp = Object.hasOwn(myObject, 'name');
类别的相关语法在 ES2022 中占了蛮大一部分的内容。其中可以把这些修订分类为-
在 ES2015 时提出的 static
关键字,只能做为公开静态方法的前缀字。不过在 ES2022 後,static
关键字的应用范围更广泛了。无论是属性或方法,公开或私有,只要在名称前加上前缀字,就能表示为静态。
目前最新版本的 Chrome 可以运作,有兴趣的话可以尝试看看。
class Inbody {
static #secretNumber = 1.2; // ES2022: 私有静态成员
static brand = 'My Inbody'; // ES2022: 公开静态成员
// ES2022: 私有静态方法
static #getPBF(weight, fat) {
return ((fat * Person.#secretNumber) / weight) * 100;
}
// ES2015: 公开静态方法
static getBMI(weight, height) {
return weight / Math.pow(height / 100, 2);
}
}
Inbody.country = 'Taiwan'; // ES2015: 公开静态成员
类别初始化时,有些静态成员需要透过流程控制设定初始值的话,通常只能在类别建立後,把相关的初始流程放在之後。在程序的语意上会像是两个独立的区块拼在一起,规模越复杂的话,就会降低维护性。
class APILibrary {
static configs;
}
try {
const fetchedConfigs = await fetchConfigs();
APILibrary.configs = { ...fetchedConfigs, tag: 1 };
} catch (error) {
APILibrary.configs = { root: 'myroot/api', tag: 2 };
}
ES2022後,类别中只要使用 static
关键字加上大括号({ }
),就能包覆静态成员的初始流程。这样做还有个好处是,流程中需要存取类别的私有成员时,可以直接取用,而不用再撰写额外存取器方法来封装,写法上算是优雅很多。
class APILibrary {
static configs;
static #defaultRoot = 'myroot/api';
static {
try {
const fetchedConfigs = await fetchConfigs();
APILibrary.configs = { ...fetchedConfigs, tag: 1 };
} catch (error) {
APILibrary.configs = { root: APILibrary.#defaultRoot, tag: 2 };
}
}
}
成为私有成员,表示只有内部可以使用,外部如果尝试存取或呼叫的话,就会回传 undefined
或错误提示。这样做可以保护成员不会被外部任意修改,或设定只有内部可以操作的方法等。
在 ES2022 後,正式把 #
作为私有成员的前缀字。像是上面的范例程序-static #defaultRoot = 'myroot/api';
,defaultRoot
属性就是一种私有属性。
需要注意的是,不只是宣告,在呼叫方法或存取属性时也需要冠上这个前缀字。更多有关私有方法与成员的范例程序与说明,可以参考我即将上市的书-ECMAScript关键30天。
await
跟 async
的语法是非同步处理的语法糖。只要函式中有 await
的关键字出现,就一定要在该函式名称的前面加 async
的前缀字。在 ES2022 後,可以允许在需要非同步的地方加上 await
就好,不需使用async函式封装,提升了撰写非同步的弹性与简洁性。
// case 1
import { fetchAppConfig } from '../APILibrary';
const appConfig = await fetchAppConfig();
// case 2
const menuData = fetch('api/config/menu.json').then((response) =>
response.json()
);
export default await menuData;
如果有在专案中使用 Babel,并且设定 @babel/preset-env
这个 preset 的话,就可以试试看目前有支援的转换语法,像是顶层 await、类别的静态初始化区块等。
如果专案是以 TypeScript 开发,在目前的 beta 版可以透过 tsconfig.json
的 module
设定来支援顶层 await。
这篇文章的内容,主要是撷取自我即将上市的书。如果有兴趣的话,也欢迎在 11/12 後去天珑翻翻看,或是在博客来等网站翻一下试阅页。有对到你的电波的话,欢迎把它带回家喔?
这篇内容也有影片版可以观看。在这里也谢谢 JSDC 的邀请,让我有了直播技术分享的初体验。另外 JSDC 当天的议程内容真的很充实,受益良多。有时间的话,会再陆续整理相关心得。
>>: I Want To Know React - PropTypes & DefaultProps
本篇同步发文於个人网站: A First Set of Refactorings This arti...
今天这篇来分享一个我觉得在介绍 Apache NiFi 的时候很典型的一个企业案例 - Renaul...
好 那今天就会完成这个小专案 可能 CSS 的部份写的没有很好 ouo 读者可以自行修改 还是再放一...
Class 是Ruby很重要的观念,要学习 Ruby 的一定要学会class & 物件。我们...
Q: 这个看起来像猫爪的东西是什麽? A: 喵? 本篇开始将实作选单的微动画,比较特别的要来说说t...