ECMAScript 新特性与标准提案

一:ES 模块

第一个要介绍的 ES 模块,由于历史上 JavaScript 没有提供模块系统,在远古时期我们常用多个 script 标签将代码进行人工隔离。但得益于浏览器和 Node.js 对于 ES 标准的讨论统一,现在我们可以在浏览器中直接书写 ES 模块语法。比如我们新建一个 lib.mjs 文件在其中导出一个函数,那么在 main.mjs 中我便可以直接导入使用它。

// lib.mjs
export const repeat = (string) => `${string} ${string}`;
// main.mjs
import {repeat} from './lib.mjs';
repeat('#io18');

而在浏览器中我们可以用 type=”module” 引入 ES 模块,我们还可以引入一个 JavaScript 文件用于兼容不支持 ES 模块写法的浏览器。加上 rel=”modulepreload” 我们可以告诉浏览器预加载一些公共库与代码。

// 浏览器
<script type="module" src="/mypath_to_js_module.mjs"></script>
<script nomodule src="fallback.js"></script>
// preload
<link rel="modulepreload" href="lib.mjs" >

上述的写法中都用到了 mjs 后缀,然而在浏览器中引用 ES 模块这种做法并不是强制的,但在 Node 实验性新特性中 mjs 是必须的。

node --experimental-modules main.mjs

兼容性如下

二:数字分隔符

给到你一串很长的数字,如何快速辨别其数值?

1000000000000
1019436871.42

我们换个写法,是不是就明确了不少:

1_000_000_000_000
1_019_436_871.42

对于非十进制数值,ES 允许我们同样用下划线进行区分

// 十六进制
0b01001001_00101111_01001111
0x23_69_6F_31_38

然而不幸的是这仍是一个处于 Stage 2 阶段的提案,但幸好我们有 Babel。兼容性如下:

三:BigInt

在 JavaScript 中安全整数范围是多少,console.log 一下。在此之前,我们若要操作超出安全整数范围的数值,结果正确性将不被得到保证,同样的问题也曾发生在 Node 上,曾有一个 issue 直指由于 Node 会偶发性给多个文件/文件夹赋值相同 inode 数。

对于超出了 Number 能够表示的安全整数范围的整数操作,我们现在可以使用 BigInt 了。虽然有很多 polyfill 支持,但现在我们得到官方支持了。

console.log(Number.MIN_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER); // -9007199254740991
// BigInt 可以直接使用,也可以在整数值后面加上n用以表示属于 BigInt 类型
BigInt(Number.MAX_SAFE_INTEGER) + 2n;
// → 9007199254740993n 正确
1234567890123456789 * 123;
// → 151851850485185200000 错误结果
1234567890123456789n * 123n;
// → 151851850485185185047n 正确
42n === BigInt(42);
typeof 123n; // 'bigint'
BigInt(1.5);
// → RangeError
BigInt('1.5');
// → SyntaxError

兼容性如下:

四:Async Iterator/Generator

我们可能习惯了这样操作一段数据读取:

const print = (readable) => {
   readable.setEncoding('utf8');
   let data = '';
   readable.on('data', (chunk) => {
       data += chunk;
   });
   readable.on('end', () => {
       console.log(data);
   })
}
const fs = require('fs');
print(fs.createReadStream('./file.txt'));

但好消息是 await 支持 for-each-of 了,于是我们可以这样写了:

async function print(readable) {
   readable.setEncoding('utf8');
   let data = '';
   for await (const chunk of readable) {
       data += chunk;
   }
   console.log(data);
}
const fs = require('fs');
print(fs.createReadStream('./file.txt'));

兼容性

五:正则匹配与字符串操作方式

现在我们来看看 dotAll 模式。字符串模版我们都用过,比如要匹配出下面的 Hello world 我们该怎么做?

const input = `
Hi, Fliggy. Hello
world.
`;
/Hello.world/u.test(input); // false

我们可能会想到.可以表示任意字符,但在这里不行,因为匹配不上换行符。于是我们可以这样做:

/Hello[\s\S]world/u.test(input); // 所有空格和所有非空格匹配
/Hello[^]world/u.test(input); // 所有非空匹配

接下来要介绍的是 Name Capture,他的作用在于将从前我们需要通过下标获取的正则匹配结果通过显式命名方法进行匹配,例如原来我们这样匹配日期:

const pattern = /(\d{4})-(\d{2})-(\d{2})/u;
const result = pattern.exec('2017-07-10');
// result[0] === '2017-07-10'
// result[1] === '2017'
// result[2] === '07'
// result[3] === '10'

现在我们可以这样写:

const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const result = pattern.exec('2017-07-10');
// result.groups.year === '2017'
// result.groups.month === '07'
// result.groups.day === '10'

对于异常复杂的正则表达式,新特性写法的优势得以体现。

第三个特性来自 Unicode 字符匹配。现在 ES 提供两种简便的匹配方式,\p{…} 用于匹配非二进制 Unicode 字符,而 \P{…} 则取其相反。

/\p{Number}/u.test('①');      // true
/\p{Alphabetic}/u.test('雪');  // true
/\P{Number}/u.test('①');      // false
/\P{Alphabetic}/u.test('雪');  // false
/^\p{Math}+$/u.test('∛∞∉');                            // true
/^\p{Script_Extensions=Hiragana}+$/u.test('ひらがな');  // true

以上所述几个方法的兼容性相同,当下 Edge 与 Firefox 还未支持。

第四个特性是字符串全匹配。通过 String matchall 特性,我们原来通过 while 循环匹配所有符合正则的写法可以直接通过 .matchAll 一次性搞定:

const string = 'Magic hex numbers: DEADBEEF CAFE 8BADF00D';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
let match;
// 旧方式
while (match = regex.exec(string)) {
   console.log(match);
}
// 新方式
for (const match of string.matchAll(regex)) {
   console.log(match);
}

新特性仍然处于 Stage 3,所以支持度比较感人,但社区已经有很多 polyfill 支持这种写法。

六:catch binding

正则总是让人难以理解,来说一个简单些的新特性—— try/catch。现在我们可以选择性的决定 catch 是否带上入参了。

try {} catch (e) {} // 以前
try {} catch {} // 现在

兼容性

七:trim

假设给你一串字符串,如果让你单独删除 hello 前部的空格或者尾部的空格,你会怎么做?

const string = '      hello        ';

以前的话,你大概率得用正则来实现,而现在 trimStart 和 trimEnd 两个方法便可以完成操作。

string.trim(); // 'hello';
string.trimStart(); // 'hello        ';
string.trimEnd(); // '      hello';

兼容性

八:Promise.prototype.finally

Promise 我们都写过,假设我们 fetch 一段数据,在结果回来之前我们需要加载 loading 动画,而结果回来后不管正确还是错误我们都需要去除这段动画。在原来,我们需要将相同的逻辑写在好几个地方(观察 isLoading = false; 写法):

let isLoading = true;
fetch(myRequest).then(function(response) {
   var contentType = response.headers.get("content-type");
   if(contentType && contentType.includes("application/json")) {
     return response.json();
   }
   throw new TypeError("Oops, we haven't got JSON!");
 })
 .then(function(json) {
   isLoading = false;
 })
 .catch(function(error) {
   isLoading = false;
   console.log(error);
 });

而现在 Promise 原型方法上补充的 finally 可以给我们减少冗余代码。

let isLoading = true;
fetch(myRequest).then(function(response) {
   var contentType = response.headers.get("content-type");
   if(contentType && contentType.includes("application/json")) {
     return response.json();
   }
   throw new TypeError("Oops, we haven't got JSON!");
 })
 .then(function(json) { /* ... */ })
 .catch(function(error) { console.log(error); })
 .finally(function() { isLoading = false; });

兼容性

九:对象解构

解构这个概念我们都不陌生,可能我们也一直在毫无感知的用着,但 ES2015 只给定了数组解构的标准,而直到2017年初针对对象的解构操作还处于 stage 3 阶段。

const person = {
   firstName: 'Sebastian',
   lastName: 'Markbåge',
   country: 'USA',
   state: 'CA',
};
const { firstName, lastName, ...rest } = person;
console.log(firstName); // Sebastian
console.log(lastName); // Markbåge
console.log(rest); // { country: 'USA', state: 'CA' }
// Spread
const personCopy = { firstName, lastName, ...rest };
console.log(personCopy);
// { firstName: 'Sebastian', lastName: 'Markbåge', country: 'USA', state: 'CA' }

在许多情况下,对象解构能为我们提供了一个更优雅的 Object.assign() 替代方案,例如合并两个对象:

const defaultSettings = { logWarnings: false, logErrors: false };
const userSettings = { logErrors: true };
// 老方式
const settings1 = Object.assign({}, defaultSettings, userSettings);
// 新方式
const settings2 = { ...defaultSettings, ...userSettings };
// 结果
// { logWarnings: false, logErrors: true }
兼容性

十:Class

最后我们来聊聊 JavaScript 中的 class。首先是字段定义,不再局限于(构造)函数中,我们可以这样定义属性 instanceProperty 和静态属性 staticProperty:

class MyClass {
   instanceProperty = 0;
   static staticProperty = 0;
}

其次是私有变量的定义与使用。在历史的长河中,JavaScript 一直缺少像其他编程语言「正规军」所有的私有变量概念,开发者长期以来都通过闭包来实现相关功能。而现在,标准赋予了 ES 这门语言拥有私有变量定义的可能性。

在使用方法上,如果需要在 class 中定义仅在类中可访问的属性,我们需要以#开头定义私有变量,就像下面这样:

class MyClass {
   #foo; // 必须声明
   constructor(foo) {
       this.#foo = foo;
   }
   incFoo() {
       this.#foo++;
   }
}

至今为止,主流浏览器和 Node 均未实现该特性。

后记

知乎上有个问题说的是「为什么那么多公司仍然在使用JDK6?」,作者困惑于 JDK11 都已发布但很多公司还在用着老旧的 Java 版本的现状;而反观 JavaScript(ECMAScript) 生态,在 ECMAScript2018 远未定稿之时,很多开发者便得心应手的用上了新语法产出代码,很多写法可能仍处于 Stage 3。Java 和 JavaScript 不仅在关系上类似于雷锋于雷峰塔,就在对待语言标准上,两边的开发者态度也是截然相反。

ECMAScript 的征程是星辰大海,跟上 TA 的脚步。

对ECMAScript的研究-----------引用的更多相关文章

  1. 对Webpack 应用的研究-----------------引用

    对大多数 Web 应用来说,页面性能直接影响着流量.这是一个经常为我们所忽视的事实.用户长时间的等待流失的不仅仅是跳出率.转化率,还有对产品的耐心和信赖.很多时候我们没有意识到性能问题,那是因为平常开 ...

  2. 对JS继承的研究--------------引用

    问:类继承和原型继承不是同一回事儿吗,只是风格选择而已? 答:不是! 类继承和原型继承不论从本质上还是从语法上来说,都是两个截然不同的概念. 二者之间有着区分彼此的本质性特征.要完全看懂本文,你必须牢 ...

  3. 对React性能优化的研究-----------------引用

    JSX的背后 这个过程一般在前端会称为“转译”,但其实“汇编”将是一个更精确的术语. React开发人员敦促你在编写组件时使用一种称为JSX的语法,混合了HTML和JavaScript.但浏览器对JS ...

  4. 对abel 转译 class 过程的研究----------------------引用

    作为当下最流行的 JavaScript 编译器,Babel 替我们转译 ECMAScript 语法,而我们不用再担心如何进行向后兼容. 零.前言 虽然在 JavaScript 中对象无处不在,但这门语 ...

  5. 对vue-router的研究--------------引用

    pushState/replaceState/popstate 解析 HTML5提供了对history栈中内容的操作.通过history.pushState/replaceState实现添加地址到hi ...

  6. 对GraphQL-BFF:微服务背景下的前后端数据交互方案的研究-------引用

    随着多终端.多平台.多业务形态.多技术选型等各方面的发展,前后端的数据交互,日益复杂. 同一份数据,可能以多种不同的形态和结构,在多种场景下被消费. 在理想情况下,这些复杂性可以全部由后端承担.前端只 ...

  7. 对 Promises/A+ 规范的研究 ------引用

    作为 Modern JavaScript 基础设施的一部分,Promises 对前端开发者而言异常重要.它是 async/await 语法的基础,是 JavaScript 中处理异步的标准形式.并且, ...

  8. 对webview的研究--------引用

    简要的说,webview能够在移动应用中开辟出一个窗口,在里面显示html页面,css以及js代码也可以被解析执行,它使用的是我们熟悉的webkit内核.android和ios都有相应的API,所以写 ...

  9. 对React的研究-------------引用

    DOM 的全称是 Document Object Model (文档对象模型) 响应式 UI const ThinkerWithHat = ({ hat }) => (  <div> ...

随机推荐

  1. 汇编语言——用DOSBox的debug查看CPU和内存 & 用机器指令和汇编指令编程

    实验一 查看CPU和内存,用机器指令和汇编指令编程   实验目的 了解什么是Debug,以及Debug中需要用的一些功能 R:查看.改变CPU寄存器的内容 D:查看内存中的内容 E:改写内存中的内容 ...

  2. 【HANA系列】SAP HANA行列转换

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA行列转换   前 ...

  3. java带图形界面的五子棋

    Main: package BlackWhite; import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowL ...

  4. python基础--导入模块

    一,import的使用1, 模块就是一组功能的集合体,我们的程序可以导入模块来复用模块中的功能一个模块就是包含了一组功能的python文件,例如demo.py 可以通过import来使用这个文件定义d ...

  5. Elasticsearch-搜索并获取数据

    Elasticsearch-搜索并获取数据 在group中搜索elasticsearch curl -XGET "localhost:9200/get-together/group/_sea ...

  6. Oracle恢复ORA-00600: 内部错误代码, 参数: [kcratr_scan_lastbwr] 问题的简单解决

    Oracle恢复ORA-00600: 内部错误代码, 参数: [kcratr_scan_lastbwr] 1. 简单处理 sqlplus / as sysdba startup mount recov ...

  7. JVM 堆内存设置原理(转)

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  8. wpf学习笔记(1)

    wpf常用控件 0x01. 常用布局控件 1.Canvas 不会对子控件施加任何帮助,也不会施加任何限制 2.DockPanel 可以让子控件贴靠在自己四条边的任意一边 ,最后一个子控件充满剩余区域 ...

  9. c++中的四种智能指针

    c++中的四种智能指针 写惯了python,golang再来写c++总觉得头大,很大一个原因就是他没有一个GC机制. 不过c++中提供了智能指针,也不是不能用,李姐万岁! auto_ptr, shar ...

  10. Ehcache 入门详解 (转)

    一:目录 EhCache 简介 Hello World 示例 Spring 整合 二: 简介 2.1.基本介绍 EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hiberna ...