2019-2020 年你可能不知道的 JavaScript 功能
私有类字段👇
String.matchAll()👇
数字分隔符👇
BigInt 的👇
带有 BigInt👇 的语言环境字符串
GlobalThis 关键字👇
Promise.allSettled()👇
动态导入👇
稳定排序 — (现在结果一致且可靠)👇
私有类字段👇
在 ES6 之前,我们无法直接声明私有属性。当然,有一些方法,例如下划线约定 (_propertyName)、闭包、符号或 WeakMap。
但是现在私有类字段使用哈希 # 前缀。让我们通过一个例子来学习一下。
class Test {
a = 1; // .a is public
#b = 2; // .#b is private
static #c = 3; // .#c is private and static
incB() {
this.#b++;
}
}
const testInstance = new Test();
// runs OK
testInstance.incB();
// error - private property cannot be modified outside class
testInstance.#b = 0;
注意:目前还无法定义私有函数,尽管TC39 第 3 阶段:草案提案建议在名称上使用哈希 # 前缀。🤞
String.matchAll()👇
如果我有一个字符串,其中有一个全局正则表达式,该表达式包含许多捕获组,我通常需要遍历所有组。目前,我的选项如下:
-
String.prototype.match() 与 /g — 如果我们使用 .match() 与设置了标志 /g 的正则表达式,则会在数组中获得它的所有完全匹配。
-
String.prototype.split() — 如果我们使用拆分字符串和正则表达式来指定分隔符,并且它包含至少一个捕获组,那么 .split() 将返回一个子字符串交错的数组。
上述方法的问题在于,只有当正则表达式设置了 /g 并且每次匹配时正则表达式的 .lastIndex 属性都会更改时,它们才有效。这使得在多个位置使用相同的正则表达式存在风险。
matchAll()函数可以解决上述所有问题。我们来看一下它的定义和用法。
给定一个字符串和一个正则表达式,.matchAll() 返回与正则表达式匹配的所有字符串结果,包括捕获组。
let regexp = /t(e)(st(\d?))/g;
let str = 'test1test2';
let array = [...str.matchAll(regexp)];
console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]
注意:.matchAll() 返回的是一个迭代器,而不是真正的可重启迭代器。也就是说,一旦结果用尽,就需要再次调用该方法并创建一个新的迭代器。
数字分隔符👇
如果您难以读懂一长串数字,那么这就是您的搜索结束的地方。
数字分隔符可以让人眼快速解析,尤其是当有大量重复数字时:
1000000000000 -> 1_000_000_000_000
1019436871.42 -> 1_019_436_871.42
现在更容易看出第一个数字是一万亿,第二个数字是十亿的数量级。
它也适用于其他基础,例如:
const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;
const words = 0xFAB_F00D;
您还可以在分数和指数中使用分隔符:
const massOfElectronInKg = 9.109_383_56e-31;
const trillionInShortScale = 1e1_2;
注意:解析以 _ 分隔的整数可能很棘手,因为 Number('123_456') 给出 NAN,而 parseInt('123_456') 给出 123。
BigInt 的👇
BigInts 是 JavaScript 中一种新的数字原语,可以表示精度大于 2⁵³-1 的整数。使用 BigInts,您可以安全地存储和操作大整数,即使它们超过了数字的安全整数限制。
BigInts 可以正确执行整数运算而不会溢出。让我们通过一个例子来理解:
const max = Number.MAX_SAFE_INTEGER;
// 9007199254740991
max+1;
// 9007199254740992
max+2;
// 9007199254740991
我们可以看到 max + 1 与 max + 2 产生相同的结果。
任何超出安全整数范围(即 Number.MIN_SAFE_INTEGER 到 Number.MAX_SAFE_INTEGER)的整数计算都可能丢失精度。因此,我们只能依赖安全范围内的整数值。
因此,BigInts 应运而生。可以通过在任何整数字面量后添加 n 后缀来创建 BigInts。例如,123 变为 123n,或者可以使用全局函数 BigInt(number) 将 Number 转换为 BigInts。
让我们用 BigInts 重新回顾一下上面的示例
BigInt(Number.MAX_SAFE_INTEGER) + 2n;
// 9007199254740993n
typeof 123n
// "bigint2"
注意:数字分隔符对于 BigInts 特别有用,例如:
const massOfEarthInKg = 6_000_000_000_000_000_000_000_000n;
BigInts 支持最常见的运算符。二进制的 +、-、和 *均能正常工作。/ 和 % 也能正常工作,并根据需要向零舍入。
(7 + 6 - 5) * 4 ** 3 / 2 % 3;
// → 1
(7n + 6n - 5n) * 4n ** 3n / 2n % 3n;
// → 1n
注意:一个问题是,不允许混合使用 BigInts 和 Numbers 之间的运算
带有 BigInt👇 的语言环境字符串
toLocaleString() 方法返回一个具有 BigInt 的语言敏感表示形式的字符串。
let bigint = 123456789123456789n;
// German uses period for thousands
console.log(bigint.toLocaleString('de-DE'));
// → 123.456.789.123.456.789
// Arabic in most Arabic speaking countries uses Eastern Arabic digits
console.log(bigint.toLocaleString('ar-EG'));
// → ١٢٣٬٤٥٦٬٧٨٩٬١٢٣٬٤٥٦٬٧٨٩
// India uses thousands/lakh/crore separators
console.log(bigint.toLocaleString('en-IN'));
// → 1,23,45,67,89,12,34,56,789
// the nu extension key requests a numbering system, e.g. Chinese decimal
console.log(bigint.toLocaleString('zh-Hans-CN-u-nu-hanidec'));
// → 一二三,四五六,七八九,一二三,四五六,七八九
// when requesting a language that may not be supported, such as
// Balinese, include a fallback language, in this case Indonesian
console.log(bigint.toLocaleString(['ban', 'id']));
// → 123.456.789.123.456.789
GlobalThis 关键字👇
JavaScript 的变量作用域是嵌套的,并形成一棵树,其根是全局作用域,而 this 关键字的值是对“拥有”当前执行的代码或所查看的函数的对象的引用。
要了解有关此关键字和全局范围的更多信息,请阅读我的以下文章
通常,为了找出全局的 this,我们使用如下函数
const getGlobalThis = () => {
// in webworker or service worker
if (typeof self !== 'undefined') return self;
// in browser
if (typeof window !== 'undefined') return window;
// in Node.js
if (typeof global !== 'undefined') return global;
// Standalone javascript shell
if (typeof this !== 'undefined') return this;
throw new Error('Unable to locate global object');
};
const theGlobalThis = getGlobalThis();
上述函数并未涵盖我们需要全局 this 值的所有情况。
在 use strict 的情况下,这个值是未定义的。
当我们在 javascript 中形成一个捆绑包时,它通常会包装在一些可能与全局 this 不同的代码下。
在独立的 javascript 引擎 shell 环境中,上述代码将不起作用。
为了解决上述问题,引入了 globalThis 关键字,它在任何环境下随时返回全局 this 对象。
注意:由于向后兼容的原因,全局对象现在被认为是 JavaScript 无法消除的错误。它会对性能产生负面影响,并且通常会造成混淆。
Promise.allSettled()👇
如果您想知道 JavaScript 中的承诺是什么,请查看这个——JavaScript承诺:简介。
简单来说,承诺是 JavaScript 向您承诺工作将会完成的方式(如果工作无法完成,则可能会失败)。
新方法返回一个承诺,该承诺在所有给定的承诺都得到解决(即解决或拒绝)后得到解决,并带有一个对象数组,每个对象描述每个承诺的结果。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));
// expected output:
// "fulfilled"
// "rejected"
这与 Promise.all 不同,因为一旦可迭代对象内的承诺被拒绝,它就会拒绝。
动态导入👇
这个很疯狂,在我们深入研究之前,让我们先看看什么是静态导入。
静态导入仅接受字符串文字作为模块说明符,并通过运行时前的“链接”过程将绑定引入本地范围。
静态导入语法只能在文件的顶层使用。
import * as module from './utils.mjs';
静态导入支持静态分析、捆绑工具和摇树等重要用例。
但是
-
按需(或有条件)导入模块
-
在运行时计算模块说明符
-
从常规脚本中导入模块(而不是模块)
在动态导入之前这是不可能的——import(moduleSpecifier)返回所请求模块的模块命名空间对象的承诺,该承诺是在获取、实例化和评估模块的所有依赖项以及模块本身之后创建的。
<script type="module">
(async () => {
const moduleSpecifier = './utils.mjs';
const module = await import(moduleSpecifier)
module.default();
// → logs 'Hi from the default export!'
module.doStuff();
// → logs 'Doing stuff…'
})();
</script>
注意:对于初始绘制依赖项(尤其是首屏内容),请使用静态导入。其他情况下,请考虑使用动态 import() 按需加载依赖项。
稳定排序 — (现在结果一致且可靠)👇
算法意义上的稳定意味着:它是否保留了顺序或其他“相等”的项目?
让我们通过一个例子来理解
const people = [
{name: 'Gary', age: 20},
{name: 'Ann', age: 20},
{name: 'Bob', age: 17},
{name: 'Sue', age: 21},
{name: 'Sam', age: 17},
];
// Sort people by name
people.sort( (p1, p2) => {
if (p1.name < p2.name) return -1;
if (p1.name > p2.name) return 1;
return 0;
});
console.log(people.map(p => p.name));
// ['Ann', 'Bob', 'Gary', 'Sam', 'Sue']
// Re-sort people by age
people.sort( (p1, p2) => {
if (p1.age < p2.age) return -1;
if (p1.age > p2.age) return 1;
return 0;
});
console.log(people.map(p => p.name));
// We're expecting people sorted by age, then by name within age group:
// ['Bob', 'Sam', 'Ann', 'Gary', 'Sue']
// But we might get any of these instead, depending on the browser:
// ['Sam', 'Bob', 'Ann', 'Gary', 'Sue']
// ['Bob', 'Sam', 'Gary', 'Ann', 'Sue']
// ['Sam', 'Bob', 'Gary', 'Ann', 'Sue']
如果您获得最后三个结果之一,那么您可能正在使用 Google Chrome,或者可能是没有将 Array.sort() 作为“稳定”算法实现的某种浏览器。
这是因为不同的 JS 引擎(跨不同的浏览器)采用不同的方式实现排序,此外,一些 javascript 引擎对短数组使用稳定排序,但对长数组使用不稳定排序。
这导致排序稳定性行为不一致,并造成很多混乱。这就是为什么在开发环境中,与排序相关的所有操作似乎都正常工作,但在生产环境中,由于测试排序的数组大小不同,我们开始看到一些异常。
注意:有第三方库,我强烈推荐Lodash,它有稳定的排序
但现在这个问题已经解决了,我们在大多数浏览器上都有了稳定的排序。语法保持不变。
由于本文有很多需要消化和试用的功能,我们将在下一篇文章中继续介绍更多新功能。
注:本文最初发表于overflowjs.com
如果您想加入我的电子邮件列表并在 dev.to 上关注我以阅读更多有关 javascript 的文章以及在GitHub上查看我的疯狂代码,请考虑在此处输入您的电子邮件。
谢谢 !
文章来源:https://dev.to/dg92/javascript-feature-you-might-not-know-in-2019-2020-4p5d