From 69cae47007874d7fd80371a5bdecf202e55fe4a4 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 25 Dec 2019 15:24:51 +0800 Subject: [PATCH] docs: update ES2020 --- docs/let.md | 2 +- docs/module.md | 32 +++++- docs/number.md | 261 ++++++++++++++++++++++++++++++++++++++++++++++ docs/proposals.md | 260 --------------------------------------------- docs/regex.md | 8 +- 5 files changed, 297 insertions(+), 266 deletions(-) diff --git a/docs/let.md b/docs/let.md index 4ec238f..b92887b 100644 --- a/docs/let.md +++ b/docs/let.md @@ -626,7 +626,7 @@ var getGlobal = function () { }; ``` -现在有一个[提案](https://github.com/tc39/proposal-global),在语言标准的层面,引入`globalThis`作为顶层对象。也就是说,任何环境下,`globalThis`都是存在的,都可以从它拿到顶层对象,指向全局环境下的`this`。 +[ES2020](https://github.com/tc39/proposal-global) 在语言标准的层面,引入`globalThis`作为顶层对象。也就是说,任何环境下,`globalThis`都是存在的,都可以从它拿到顶层对象,指向全局环境下的`this`。 垫片库[`global-this`](https://github.com/ungap/global-this)模拟了这个提案,可以在所有环境拿到`globalThis`。 diff --git a/docs/module.md b/docs/module.md index 4c3141d..2760b80 100644 --- a/docs/module.md +++ b/docs/module.md @@ -670,7 +670,7 @@ const myModual = require(path); 上面的语句就是动态加载,`require`到底加载哪一个模块,只有运行时才知道。`import`命令做不到这一点。 -因此,有一个[提案](https://github.com/tc39/proposal-dynamic-import),建议引入`import()`函数,完成动态加载。 +[ES2020提案](https://github.com/tc39/proposal-dynamic-import) 引入`import()`函数,支持动态加载模块。 ```javascript import(specifier) @@ -800,3 +800,33 @@ async function main() { } main(); ``` + +## import.meta + +开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。现在有一个[提案](https://github.com/tc39/proposal-import-meta),为 import 命令添加了一个元属性`import.beta`,返回当前模块的元信息。 + +`import.meta`只能在模块内部使用,如果在模块外部使用会报错。 + +**(1)import.meta.url** + +`import.meta.url`返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是`https://foo.com/main.js`,`import.meta.url`就返回这个路径。如果模块里面还有一个数据文件`data.txt`,那么就可以用下面的代码,获取这个数据文件的路径。 + +```javascript +new URL('data.txt', import.meta.url) +``` + +注意,Node.js 环境中,`import.meta.url`返回的总是本地路径,即是`file:URL`协议的字符串,比如`file:///home/user/foo.js`。 + +**(2)import.meta.scriptElement** + +`import.meta.scriptElement`是浏览器特有的元属性,返回加载模块的那个` + +// my-module.js 内部执行下面的代码 +import.meta.scriptElement.dataset.foo +// "abc" +``` + diff --git a/docs/number.md b/docs/number.md index 677114d..25611e4 100644 --- a/docs/number.md +++ b/docs/number.md @@ -697,3 +697,264 @@ Math.pow(99, 99) ``` 上面代码中,两个运算结果的最后一位有效数字是有差异的。 + +## BigInt 数据类型 + +### 简介 + +JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回`Infinity`。 + +```javascript +// 超过 53 个二进制位的数值,无法保持精度 +Math.pow(2, 53) === Math.pow(2, 53) + 1 // true + +// 超过 2 的 1024 次方的数值,无法表示 +Math.pow(2, 1024) // Infinity +``` + +[ES2020](https://github.com/tc39/proposal-bigint) 引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。 + +```javascript +const a = 2172141653n; +const b = 15346349309n; + +// BigInt 可以保持精度 +a * b // 33334444555566667777n + +// 普通整数无法保持精度 +Number(a) * Number(b) // 33334444555566670000 +``` + +为了与 Number 类型区别,BigInt 类型的数据必须添加后缀`n`。 + +```javascript +1234 // 普通整数 +1234n // BigInt + +// BigInt 的运算 +1n + 2n // 3n +``` + +BigInt 同样可以使用各种进制表示,都要加上后缀`n`。 + +```javascript +0b1101n // 二进制 +0o777n // 八进制 +0xFFn // 十六进制 +``` + +BigInt 与普通整数是两种值,它们之间并不相等。 + +```javascript +42n === 42 // false +``` + +`typeof`运算符对于 BigInt 类型的数据返回`bigint`。 + +```javascript +typeof 123n // 'bigint' +``` + +BigInt 可以使用负号(`-`),但是不能使用正号(`+`),因为会与 asm.js 冲突。 + +```javascript +-42n // 正确 ++42n // 报错 +``` + +JavaScript 以前不能计算70的阶乘(即`70!`),因为超出了可以表示的精度。 + +```javascript +let p = 1; +for (let i = 1; i <= 70; i++) { + p *= i; +} +console.log(p); // 1.197857166996989e+100 +``` + +现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。 + +```javascript +let p = 1n; +for (let i = 1n; i <= 70n; i++) { + p *= i; +} +console.log(p); // 11978571...00000000n +``` + +### BigInt 对象 + +JavaScript 原生提供`BigInt`对象,可以用作构造函数生成 BigInt 类型的数值。转换规则基本与`Number()`一致,将其他类型的值转为 BigInt。 + +```javascript +BigInt(123) // 123n +BigInt('123') // 123n +BigInt(false) // 0n +BigInt(true) // 1n +``` + +`BigInt()`构造函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。 + +```javascript +new BigInt() // TypeError +BigInt(undefined) //TypeError +BigInt(null) // TypeError +BigInt('123n') // SyntaxError +BigInt('abc') // SyntaxError +``` + +上面代码中,尤其值得注意字符串`123n`无法解析成 Number 类型,所以会报错。 + +参数如果是小数,也会报错。 + +```javascript +BigInt(1.5) // RangeError +BigInt('1.5') // SyntaxError +``` + +BigInt 对象继承了 Object 对象的两个实例方法。 + +- `BigInt.prototype.toString()` +- `BigInt.prototype.valueOf()` + +它还继承了 Number 对象的一个实例方法。 + +- `BigInt.prototype.toLocaleString()` + +此外,还提供了三个静态方法。 + +- `BigInt.asUintN(width, BigInt)`: 给定的 BigInt 转为 0 到 2width - 1 之间对应的值。 +- `BigInt.asIntN(width, BigInt)`:给定的 BigInt 转为 -2width - 1 到 2width - 1 - 1 之间对应的值。 +- `BigInt.parseInt(string[, radix])`:近似于`Number.parseInt()`,将一个字符串转换成指定进制的 BigInt。 + +```javascript +const max = 2n ** (64n - 1n) - 1n; + +BigInt.asIntN(64, max) +// 9223372036854775807n +BigInt.asIntN(64, max + 1n) +// -9223372036854775808n +BigInt.asUintN(64, max + 1n) +// 9223372036854775808n +``` + +上面代码中,`max`是64位带符号的 BigInt 所能表示的最大值。如果对这个值加`1n`,`BigInt.asIntN()`将会返回一个负值,因为这时新增的一位将被解释为符号位。而`BigInt.asUintN()`方法由于不存在符号位,所以可以正确返回结果。 + +如果`BigInt.asIntN()`和`BigInt.asUintN()`指定的位数,小于数值本身的位数,那么头部的位将被舍弃。 + +```javascript +const max = 2n ** (64n - 1n) - 1n; + +BigInt.asIntN(32, max) // -1n +BigInt.asUintN(32, max) // 4294967295n +``` + +上面代码中,`max`是一个64位的 BigInt,如果转为32位,前面的32位都会被舍弃。 + +下面是`BigInt.parseInt()`的例子。 + +```javascript +// Number.parseInt() 与 BigInt.parseInt() 的对比 +Number.parseInt('9007199254740993', 10) +// 9007199254740992 +BigInt.parseInt('9007199254740993', 10) +// 9007199254740993n +``` + +上面代码中,由于有效数字超出了最大限度,`Number.parseInt`方法返回的结果是不精确的,而`BigInt.parseInt`方法正确返回了对应的 BigInt。 + +对于二进制数组,BigInt 新增了两个类型`BigUint64Array`和`BigInt64Array`,这两种数据类型返回的都是64位 BigInt。`DataView`对象的实例方法`DataView.prototype.getBigInt64()`和`DataView.prototype.getBigUint64()`,返回的也是 BigInt。 + +### 转换规则 + +可以使用`Boolean()`、`Number()`和`String()`这三个方法,将 BigInt 可以转为布尔值、数值和字符串类型。 + +```javascript +Boolean(0n) // false +Boolean(1n) // true +Number(1n) // 1 +String(1n) // "1" +``` + +上面代码中,注意最后一个例子,转为字符串时后缀`n`会消失。 + +另外,取反运算符(`!`)也可以将 BigInt 转为布尔值。 + +```javascript +!0n // true +!1n // false +``` + +### 数学运算 + +数学运算方面,BigInt 类型的`+`、`-`、`*`和`**`这四个二元运算符,与 Number 类型的行为一致。除法运算`/`会舍去小数部分,返回一个整数。 + +```javascript +9n / 5n +// 1n +``` + +几乎所有的数值运算符都可以用在 BigInt,但是有两个例外。 + +- 不带符号的右移位运算符`>>>` +- 一元的求正运算符`+` + +上面两个运算符用在 BigInt 会报错。前者是因为`>>>`运算符是不带符号的,但是 BigInt 总是带有符号的,导致该运算无意义,完全等同于右移运算符`>>`。后者是因为一元运算符`+`在 asm.js 里面总是返回 Number 类型,为了不破坏 asm.js 就规定`+1n`会报错。 + +BigInt 不能与普通数值进行混合运算。 + +```javascript +1n + 1.3 // 报错 +``` + +上面代码报错是因为无论返回的是 BigInt 或 Number,都会导致丢失精度信息。比如`(2n**53n + 1n) + 0.5`这个表达式,如果返回 BigInt 类型,`0.5`这个小数部分会丢失;如果返回 Number 类型,有效精度只能保持 53 位,导致精度下降。 + +同样的原因,如果一个标准库函数的参数预期是 Number 类型,但是得到的是一个 BigInt,就会报错。 + +```javascript +// 错误的写法 +Math.sqrt(4n) // 报错 + +// 正确的写法 +Math.sqrt(Number(4n)) // 2 +``` + +上面代码中,`Math.sqrt`的参数预期是 Number 类型,如果是 BigInt 就会报错,必须先用`Number`方法转一下类型,才能进行计算。 + +asm.js 里面,`|0`跟在一个数值的后面会返回一个32位整数。根据不能与 Number 类型混合运算的规则,BigInt 如果与`|0`进行运算会报错。 + +```javascript +1n | 0 // 报错 +``` + +### 其他运算 + +BigInt 对应的布尔值,与 Number 类型一致,即`0n`会转为`false`,其他值转为`true`。 + +```javascript +if (0n) { + console.log('if'); +} else { + console.log('else'); +} +// else +``` + +上面代码中,`0n`对应`false`,所以会进入`else`子句。 + +比较运算符(比如`>`)和相等运算符(`==`)允许 BigInt 与其他类型的值混合计算,因为这样做不会损失精度。 + +```javascript +0n < 1 // true +0n < true // true +0n == 0 // true +0n == false // true +0n === 0 // false +``` + +BigInt 与字符串混合运算时,会先转为字符串,再进行运算。 + +```javascript +'' + 123n // "123" +``` + diff --git a/docs/proposals.md b/docs/proposals.md index 50097f3..6d9c35e 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -391,266 +391,6 @@ Number('123_456') // NaN parseInt('123_456') // 123 ``` -## BigInt 数据类型 - -### 简介 - -JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回`Infinity`。 - -```javascript -// 超过 53 个二进制位的数值,无法保持精度 -Math.pow(2, 53) === Math.pow(2, 53) + 1 // true - -// 超过 2 的 1024 次方的数值,无法表示 -Math.pow(2, 1024) // Infinity -``` - -现在有一个[提案](https://github.com/tc39/proposal-bigint),引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。 - -```javascript -const a = 2172141653n; -const b = 15346349309n; - -// BigInt 可以保持精度 -a * b // 33334444555566667777n - -// 普通整数无法保持精度 -Number(a) * Number(b) // 33334444555566670000 -``` - -为了与 Number 类型区别,BigInt 类型的数据必须添加后缀`n`。 - -```javascript -1234 // 普通整数 -1234n // BigInt - -// BigInt 的运算 -1n + 2n // 3n -``` - -BigInt 同样可以使用各种进制表示,都要加上后缀`n`。 - -```javascript -0b1101n // 二进制 -0o777n // 八进制 -0xFFn // 十六进制 -``` - -BigInt 与普通整数是两种值,它们之间并不相等。 - -```javascript -42n === 42 // false -``` - -`typeof`运算符对于 BigInt 类型的数据返回`bigint`。 - -```javascript -typeof 123n // 'bigint' -``` - -BigInt 可以使用负号(`-`),但是不能使用正号(`+`),因为会与 asm.js 冲突。 - -```javascript --42n // 正确 -+42n // 报错 -``` - -JavaScript 以前不能计算70的阶乘(即`70!`),因为超出了可以表示的精度。 - -```javascript -let p = 1; -for (let i = 1; i <= 70; i++) { - p *= i; -} -console.log(p); // 1.197857166996989e+100 -``` - -现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。 - -```javascript -let p = 1n; -for (let i = 1n; i <= 70n; i++) { - p *= i; -} -console.log(p); // 11978571...00000000n -``` - -### BigInt 对象 - -JavaScript 原生提供`BigInt`对象,可以用作构造函数生成 BigInt 类型的数值。转换规则基本与`Number()`一致,将其他类型的值转为 BigInt。 - -```javascript -BigInt(123) // 123n -BigInt('123') // 123n -BigInt(false) // 0n -BigInt(true) // 1n -``` - -`BigInt()`构造函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。 - -```javascript -new BigInt() // TypeError -BigInt(undefined) //TypeError -BigInt(null) // TypeError -BigInt('123n') // SyntaxError -BigInt('abc') // SyntaxError -``` - -上面代码中,尤其值得注意字符串`123n`无法解析成 Number 类型,所以会报错。 - -参数如果是小数,也会报错。 - -```javascript -BigInt(1.5) // RangeError -BigInt('1.5') // SyntaxError -``` - -BigInt 对象继承了 Object 对象的两个实例方法。 - -- `BigInt.prototype.toString()` -- `BigInt.prototype.valueOf()` - -它还继承了 Number 对象的一个实例方法。 - -- `BigInt.prototype.toLocaleString()` - -此外,还提供了三个静态方法。 - -- `BigInt.asUintN(width, BigInt)`: 给定的 BigInt 转为 0 到 2width - 1 之间对应的值。 -- `BigInt.asIntN(width, BigInt)`:给定的 BigInt 转为 -2width - 1 到 2width - 1 - 1 之间对应的值。 -- `BigInt.parseInt(string[, radix])`:近似于`Number.parseInt()`,将一个字符串转换成指定进制的 BigInt。 - -```javascript -const max = 2n ** (64n - 1n) - 1n; - -BigInt.asIntN(64, max) -// 9223372036854775807n -BigInt.asIntN(64, max + 1n) -// -9223372036854775808n -BigInt.asUintN(64, max + 1n) -// 9223372036854775808n -``` - -上面代码中,`max`是64位带符号的 BigInt 所能表示的最大值。如果对这个值加`1n`,`BigInt.asIntN()`将会返回一个负值,因为这时新增的一位将被解释为符号位。而`BigInt.asUintN()`方法由于不存在符号位,所以可以正确返回结果。 - -如果`BigInt.asIntN()`和`BigInt.asUintN()`指定的位数,小于数值本身的位数,那么头部的位将被舍弃。 - -```javascript -const max = 2n ** (64n - 1n) - 1n; - -BigInt.asIntN(32, max) // -1n -BigInt.asUintN(32, max) // 4294967295n -``` - -上面代码中,`max`是一个64位的 BigInt,如果转为32位,前面的32位都会被舍弃。 - -下面是`BigInt.parseInt()`的例子。 - -```javascript -// Number.parseInt() 与 BigInt.parseInt() 的对比 -Number.parseInt('9007199254740993', 10) -// 9007199254740992 -BigInt.parseInt('9007199254740993', 10) -// 9007199254740993n -``` - -上面代码中,由于有效数字超出了最大限度,`Number.parseInt`方法返回的结果是不精确的,而`BigInt.parseInt`方法正确返回了对应的 BigInt。 - -对于二进制数组,BigInt 新增了两个类型`BigUint64Array`和`BigInt64Array`,这两种数据类型返回的都是64位 BigInt。`DataView`对象的实例方法`DataView.prototype.getBigInt64()`和`DataView.prototype.getBigUint64()`,返回的也是 BigInt。 - -### 转换规则 - -可以使用`Boolean()`、`Number()`和`String()`这三个方法,将 BigInt 可以转为布尔值、数值和字符串类型。 - -```javascript -Boolean(0n) // false -Boolean(1n) // true -Number(1n) // 1 -String(1n) // "1" -``` - -上面代码中,注意最后一个例子,转为字符串时后缀`n`会消失。 - -另外,取反运算符(`!`)也可以将 BigInt 转为布尔值。 - -```javascript -!0n // true -!1n // false -``` - -### 数学运算 - -数学运算方面,BigInt 类型的`+`、`-`、`*`和`**`这四个二元运算符,与 Number 类型的行为一致。除法运算`/`会舍去小数部分,返回一个整数。 - -```javascript -9n / 5n -// 1n -``` - -几乎所有的数值运算符都可以用在 BigInt,但是有两个例外。 - -- 不带符号的右移位运算符`>>>` -- 一元的求正运算符`+` - -上面两个运算符用在 BigInt 会报错。前者是因为`>>>`运算符是不带符号的,但是 BigInt 总是带有符号的,导致该运算无意义,完全等同于右移运算符`>>`。后者是因为一元运算符`+`在 asm.js 里面总是返回 Number 类型,为了不破坏 asm.js 就规定`+1n`会报错。 - -BigInt 不能与普通数值进行混合运算。 - -```javascript -1n + 1.3 // 报错 -``` - -上面代码报错是因为无论返回的是 BigInt 或 Number,都会导致丢失精度信息。比如`(2n**53n + 1n) + 0.5`这个表达式,如果返回 BigInt 类型,`0.5`这个小数部分会丢失;如果返回 Number 类型,有效精度只能保持 53 位,导致精度下降。 - -同样的原因,如果一个标准库函数的参数预期是 Number 类型,但是得到的是一个 BigInt,就会报错。 - -```javascript -// 错误的写法 -Math.sqrt(4n) // 报错 - -// 正确的写法 -Math.sqrt(Number(4n)) // 2 -``` - -上面代码中,`Math.sqrt`的参数预期是 Number 类型,如果是 BigInt 就会报错,必须先用`Number`方法转一下类型,才能进行计算。 - -asm.js 里面,`|0`跟在一个数值的后面会返回一个32位整数。根据不能与 Number 类型混合运算的规则,BigInt 如果与`|0`进行运算会报错。 - -```javascript -1n | 0 // 报错 -``` - -### 其他运算 - -BigInt 对应的布尔值,与 Number 类型一致,即`0n`会转为`false`,其他值转为`true`。 - -```javascript -if (0n) { - console.log('if'); -} else { - console.log('else'); -} -// else -``` - -上面代码中,`0n`对应`false`,所以会进入`else`子句。 - -比较运算符(比如`>`)和相等运算符(`==`)允许 BigInt 与其他类型的值混合计算,因为这样做不会损失精度。 - -```javascript -0n < 1 // true -0n < true // true -0n == 0 // true -0n == false // true -0n === 0 // false -``` - -BigInt 与字符串混合运算时,会先转为字符串,再进行运算。 - -```javascript -'' + 123n // "123" -``` - ## Math.signbit() `Math.sign()`用来判断一个值的正负,但是如果参数是`-0`,它会返回`-0`。 diff --git a/docs/regex.md b/docs/regex.md index 370b01f..a64ad1e 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -594,7 +594,7 @@ RE_TWICE.test('abc!abc!abc') // true RE_TWICE.test('abc!abc!ab') // false ``` -## String.prototype.matchAll +## String.prototype.matchAll() 如果一个正则表达式在字符串里面有多个匹配,现在一般使用`g`修饰符或`y`修饰符,在循环里面逐一取出。 @@ -618,7 +618,7 @@ matches 上面代码中,`while`循环取出每一轮的正则匹配,一共三轮。 -目前有一个[提案](https://github.com/tc39/proposal-string-matchall),增加了`String.prototype.matchAll`方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。 +[ES2020](https://github.com/tc39/proposal-string-matchall) 增加了`String.prototype.matchAll()`方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。 ```javascript const string = 'test1test2test3'; @@ -636,12 +636,12 @@ for (const match of string.matchAll(regex)) { 上面代码中,由于`string.matchAll(regex)`返回的是遍历器,所以可以用`for...of`循环取出。相对于返回数组,返回遍历器的好处在于,如果匹配结果是一个很大的数组,那么遍历器比较节省资源。 -遍历器转为数组是非常简单的,使用`...`运算符和`Array.from`方法就可以了。 +遍历器转为数组是非常简单的,使用`...`运算符和`Array.from()`方法就可以了。 ```javascript // 转为数组方法一 [...string.matchAll(regex)] // 转为数组方法二 -Array.from(string.matchAll(regex)); +Array.from(string.matchAll(regex)) ```