探索-JavaScript-ES2025-版--三-
探索 JavaScript(ES2025 版)(三)
原文:
exploringjs.com/js/book/index.html译者:飞龙
IV 原始值
原文:exploringjs.com/js/book/pt_primitive-values.html
16 非值 undefined 和 null
-
16.1
undefined与null的比较 -
16.2
undefined和null的出现情况-
16.2.1
undefined的出现情况 -
16.2.2
null的出现情况
-
-
16.3 检查
undefined或null -
16.4 用于默认值的空值合并运算符 (
??) (ES2020)-
16.4.0.1
??是短路操作 -
16.4.1 示例:计算匹配项
-
16.4.2 示例:为属性指定默认值
-
16.4.3 传统方法:使用逻辑或 (
||) 设置默认值 -
16.4.4 空值合并赋值运算符 (
??=) (ES2021)
-
-
16.5
undefined和null没有属性 -
16.6
undefined和null的历史
许多编程语言都有一个名为 null 的“非值”。它表示变量当前不指向对象——例如,当它尚未初始化时。
相比之下,JavaScript 有两个这样的值:undefined 和 null。
16.1 undefined 与 null 的比较
这两个值非常相似,通常可以互换使用。它们之间的区别因此很微妙。语言本身做出了以下区分:
-
undefined表示“未初始化”(例如,一个变量)或“不存在”(例如,一个对象的属性)。 -
null表示“任何对象值的故意缺失”(来自 语言规范 的引用)。
程序员有时会做出以下区分:
-
undefined是语言中使用的非值(当某物未初始化时等)。 -
null表示“显式关闭”。也就是说,它有助于实现一种包含有意义的值和表示“无有意义的值”的元值的类型。这种类型在函数式编程中称为 选项类型 或 可能类型。
16.2 undefined 和 null 的出现情况
以下小节描述了 undefined 和 null 在语言中的出现位置。我们将在本书的后续部分更详细地解释几个机制。
16.2.1 undefined 的出现情况
未初始化的变量 myVar:
let myVar;
assert.equal(myVar, undefined);
参数 x 未提供:
function func(x) {
return x;
}
assert.equal(func(), undefined);
属性 .unknownProp 缺失:
const obj = {};
assert.equal(obj.unknownProp, undefined);
如果我们没有通过 return 语句显式指定函数的结果,JavaScript 会为我们返回 undefined:
function func() {}
assert.equal(func(), undefined);
16.2.2 null 的出现情况
对象的原型要么是一个对象,要么是原型链末尾的 null。Object.prototype 没有原型:
> Object.getPrototypeOf(Object.prototype)
null
如果我们将正则表达式(例如 /a/)与字符串(例如 'x')匹配,我们要么得到一个包含匹配数据的对象(如果匹配成功),要么得到 null(如果匹配失败):
> /a/.exec('x')
null
JSON 数据格式 不支持 undefined,只支持 null:
> JSON.stringify({a: undefined, b: null})
'{"b":null}'
16.3 检查 undefined 或 null
检查以下任一:
if (x === null) ···
if (x === undefined) ···
x 有值吗?
if (x !== undefined && x !== null) {
// ···
}
if (x) { // truthy?
// x is neither: undefined, null, false, 0, NaN, 0n, ''
}
x 是 undefined 或 null 吗?
if (x === undefined || x === null) {
// ···
}
if (!x) { // falsy?
// x is: undefined, null, false, 0, NaN, 0n, ''
}
真值意味着“如果转换为布尔值则为 true”。假值意味着“如果转换为布尔值则为 false”。这两个概念在“假值和真值” (§17.2) 中得到了适当的解释。
16.4 空值合并运算符 (??) 用于默认值 (ES2020)
空值合并运算符(??)允许我们在值是 undefined 或 null 时使用默认值:
value ?? defaultValue
-
如果
value不是undefined或null,则评估defaultValue并返回结果。 -
否则,返回
value。
示例:
> undefined ?? 'default'
'default'
> null ?? 'default'
'default'
> false ?? 'default'
false
> 0 ?? 'default'
0
> '' ?? 'default'
''
> {} ?? 'default'
{}
16.4.0.1 ?? 是短路操作
?? 是短路操作 – 只有当右侧实际上被使用时,它才会被评估:
let evaluated = false;
// Right-hand side is not used
123 ?? (evaluated = true);
assert.equal(evaluated, false);
// Right-hand side is used
undefined ?? (evaluated = true);
assert.equal(evaluated, true);
16.4.1 示例:计数火柴
以下代码展示了现实世界的一个例子:
function countMatches(regex, str) {
const matchResult = str.match(regex); // null or Array
return (matchResult ?? []).length;
}
assert.equal(
countMatches(/a/g, 'ababa'), 3
);
assert.equal(
countMatches(/b/g, 'ababa'), 2
);
assert.equal(
countMatches(/x/g, 'ababa'), 0
);
如果 regex 在 str 内有一个或多个匹配项,则 .match() 返回一个数组。如果没有匹配项,它不幸地返回 null(而不是空数组)。我们通过 ?? 运算符来修复这个问题。
我们也可以使用 可选链:
return matchResult?.length ?? 0;
16.4.2 示例:为属性指定默认值
function getTitle(fileDesc) {
return fileDesc.title ?? '(Untitled)';
}
const files = [
{ path: 'index.html', title: 'Home' },
{ path: 'tmp.html' },
];
assert.deepEqual(
files.map(f => getTitle(f)),
['Home', '(Untitled)']
);
16.4.3 旧方法:使用逻辑或 (||) 作为默认值
在 ECMAScript 2020 和空值合并运算符之前,逻辑或用于默认值。这有一个缺点。
|| 对 undefined 和 null 的处理是预期的:
> undefined || 'default'
'default'
> null || 'default'
'default'
但它也返回所有其他假值(例如)的默认值:
> false || 'default'
'default'
> 0 || 'default'
'default'
> 0n || 'default'
'default'
> '' || 'default'
'default'
将此与 ?? 的工作方式进行比较:
> undefined ?? 'default'
'default'
> null ?? 'default'
'default'
> false ?? 'default'
false
> 0 ?? 'default'
0
> 0n ?? 'default'
0n
> '' ?? 'default'
''
16.4.4 空值合并赋值运算符 (??=) (ES2021)
空值合并赋值运算符(??=)在值是 undefined 或 null 时分配一个默认值:
value ??= defaultValue
-
如果
value是undefined或null,则评估defaultValue并将其分配给value。 -
否则,不发生任何操作。
示例:
let value;
value = undefined;
value ??= 'DEFAULT';
assert.equal(
value, 'DEFAULT'
);
value = 0;
value ??= 'DEFAULT';
assert.equal(
value, 0
);
16.4.4.1 ??= 是短路操作
以下两个表达式大致等价:
a ??= b
a ?? (a = b)
这意味着 ??= 是短路操作 – 只有当 a 是 undefined 或 null 时,以下两个操作才会发生:
-
b被评估。 -
结果被分配给
a。
let value;
value = undefined;
value ??= console.log('evaluated');
value = 0;
value ??= console.log('NOT EVALUATED');
evaluated
16.4.4.2 示例:使用 ??= 添加缺失的属性
const books = [
{
isbn: '123',
},
{
title: 'ECMAScript Language Specification',
isbn: '456',
},
];
// Add property .title where it’s missing
for (const book of books) {
book.title ??= '(Untitled)';
}
assert.deepEqual(
books,
[
{
isbn: '123',
title: '(Untitled)',
},
{
title: 'ECMAScript Language Specification',
isbn: '456',
},
]);
16.5 undefined 和 null 没有属性
undefined 和 null 是 JavaScript 中唯一两个尝试读取属性时会抛出异常的值。为了探索这一现象,让我们使用以下函数,该函数读取(获取)属性 .prop 并返回结果。
function getProp(x) {
return x.prop;
}
如果我们将 getProp() 应用到各种值上,我们可以看到它只对 undefined 和 null 失败:
> getProp(undefined)
TypeError: Cannot read properties of undefined (reading 'prop')
> getProp(null)
TypeError: Cannot read properties of null (reading 'prop')
> getProp(true)
undefined
> getProp({})
undefined
16.6 undefined 和 null 的历史
在 Java(它启发了 JavaScript 的许多方面)中,初始化值取决于变量的静态类型:
-
对象类型的变量使用
null进行初始化。 -
每个原始类型都有自己的初始化值。例如,
int类型的变量初始化为0。
JavaScript 借用了 null 并在期望对象的地方使用它。它意味着“不是一个对象”。
然而,JavaScript 中的存储位置(变量、属性等)可以存储原始值或对象。它们需要一个表示“既不是对象也不是原始值”的初始化值。这就是为什么引入了 undefined 的原因。
17 布尔值
-
17.1 转换为布尔值
-
17.2 假值和真值
- 17.2.1 检查真值或假值
-
17.3 基于真值的检查
-
17.3.1 陷阱:基于真值的存在检查是不精确的
-
17.3.2 用例:是否提供了参数?
-
17.3.3 用例:属性是否存在?
-
-
17.4 条件运算符(
? :) -
17.5 二元逻辑运算符:与(
x && y),或(x || y)-
17.5.1 值保留
-
17.5.2 短路
-
17.5.3 逻辑与 (
x && y) -
17.5.4 逻辑或 (
||)
-
-
17.6 逻辑非(
!)
原始类型布尔包含两个值——false和true:
> typeof false
'boolean'
> typeof true
'boolean'
17.1 转换为布尔值
“转换为[type]”的含义
“转换为[type]”是“将任意值转换为[type]类型值”的简称。
这是我们将任意值x转换为布尔值的三种方式。
-
Boolean(x)最具描述性;推荐。
-
x ? true : false使用条件运算符(将在本章后面解释条件运算符)。
-
!!x使用逻辑非运算符(
!)。此运算符将操作数强制转换为布尔值。它被第二次应用以获取非取反的结果。
表 17.1 描述了各种值如何转换为布尔值。
x |
Boolean(x) |
|---|---|
undefined |
false |
null |
false |
| 布尔 | x(无变化) |
| 数字 | 0 → false,NaN → false |
其他数字 → true |
|
| 大整数 | 0 → false |
其他数字 → true |
|
| 字符串 | '' → false |
其他字符串 → true |
|
| 符号 | true |
| 对象 | 总是true |
表 17.1:将值转换为布尔值。
17.2 假值和真值
在 JavaScript 期望布尔值的大多数位置,我们可以使用任意值,JavaScript 会在使用之前将其转换为布尔值。例如包括:
-
if语句的条件 -
while循环的条件 -
do-while循环的条件
考虑以下 if 语句:
if (value) {}
在许多编程语言中,此条件等同于:
if (value === true) {}
然而,在 JavaScript 中,它等同于:
if (Boolean(value) === true) {}
即,JavaScript 检查value转换为布尔值时是否为true。这种检查非常常见,因此引入了以下名称:
-
当一个值转换为布尔值时,如果它是
true,则称为真值。 -
当一个值转换为布尔值时,如果它是
false,则称为假值。
每个值要么是真值,要么是假值。这是假值的不完全列表:
-
undefined -
null -
布尔:
false -
数字:
0,NaN -
BigInt:
0n -
String:
''
所有其他值(包括所有对象)都是真值:
> Boolean('abc')
true
> Boolean([])
true
> Boolean({})
true
17.2.1 检查真值或假值
if (x) {
// x is truthy
}
if (!x) {
// x is falsy
}
if (x) {
// x is truthy
} else {
// x is falsy
}
const result = x ? 'truthy' : 'falsy';
在最后一行使用的条件操作符,将在本章后面解释条件操作符。
练习:真值
exercises/booleans/truthiness_exrc.mjs
17.3 基于真值的存在检查
在 JavaScript 中,如果我们读取不存在的某个值(例如,缺失的参数或缺失的属性),我们通常得到undefined作为结果。在这些情况下,存在检查相当于比较一个值与undefined。例如,以下代码检查对象obj是否有属性.prop:
if (obj.prop !== undefined) {
// obj has property .prop
}
由于undefined是假值,我们可以将这个检查简化为:
if (obj.prop) {
// obj has property .prop
}
17.3.1 陷阱:基于真值的存在检查是不精确的
基于真值的存在检查有一个陷阱:它们不是很精确。考虑这个先前的例子:
if (obj.prop) {
// obj has property .prop
}
如果以下条件成立,则跳过if语句的主体:
obj.prop缺失(在这种情况下,JavaScript 返回undefined)。
然而,如果以下条件成立,它也会被跳过:
-
obj.prop是undefined。 -
obj.prop是任何其他假值(null,0,''等)。
在实践中,这很少引起问题,但我们必须意识到这个陷阱。
17.3.2 用例:参数是否提供?
真值检查常用于确定函数的调用者是否提供了参数:
function func(x) {
if (!x) {
throw new Error('Missing parameter x');
}
// ···
}
优点在于,这种模式已经建立并且简短。它正确地抛出undefined和null的错误。
缺点在于,之前提到的陷阱:代码也会为所有其他假值抛出错误。
另一个选择是检查undefined:
if (x === undefined) {
throw new Error('Missing parameter x');
}
17.3.3 用例:属性是否存在?
真值检查也常用于确定属性是否存在:
function readFile(fileDesc) {
if (!fileDesc.path) {
throw new Error('Missing property: .path');
}
// ···
}
readFile({ path: 'foo.txt' }); // no error
这种模式已经建立,并且有通常的注意事项:它不仅会在属性缺失时抛出错误,而且如果属性存在并且具有任何假值也会抛出错误。
如果我们真正想检查属性是否存在,我们必须使用in操作符:
if (! ('path' in fileDesc)) {
throw new Error('Missing property: .path');
}
17.4 条件操作符(? :)
条件操作符是if语句的表达式版本。其语法是:
«condition» ? «thenExpression» : «elseExpression»
它的评估方式如下:
-
如果
condition是真值,则评估并返回thenExpression。 -
否则,评估并返回
elseExpression。
条件操作符也被称为三元操作符,因为它有三个操作数。
示例:
> true ? 'yes' : 'no'
'yes'
> false ? 'yes' : 'no'
'no'
> '' ? 'yes' : 'no'
'no'
以下代码演示了无论通过条件选择的是“then”分支还是“else”分支,只有那个分支会被评估。另一个分支则不会。
const x = (true ? console.log('then') : console.log('else'));
输出:
then
17.5 二元逻辑运算符:与(x && y)、或(x || y)
JavaScript 有两种二元逻辑运算符:
-
逻辑与(
x && y) -
逻辑或(
x || y)
它们是值保留和短路的。
17.5.1 值保留
值保留意味着操作数被解释为布尔值,但返回时不改变:
> 12 || 'hello'
12
> 0 || 'hello'
'hello'
17.5.2 短路
短路意味着如果第一个操作数已经决定了结果,则不会评估第二个操作数。唯一其他延迟评估其操作数的运算符是条件运算符。通常,在执行操作之前,所有操作数都会被评估。
例如,逻辑与(&&)如果第一个操作数是假值,则不会评估其第二个操作数:
const x = false && console.log('hello');
// No output
如果第一个操作数是真值,则执行console.log():
const x = true && console.log('hello');
输出:
hello
17.5.3 逻辑与(x && y)
表达式a && b(“a 与 b”)的评估如下:
-
评估
a。 -
结果是否为假值?返回它。
-
否则,评估
b并返回结果。
换句话说,以下两个表达式大致等价:
a && b
!a ? a : b
示例:
> false && true
false
> false && 'abc'
false
> true && false
false
> true && 'abc'
'abc'
> '' && 'abc'
''
17.5.4 逻辑或(||)
表达式a || b(“a 或 b”)的评估如下:
-
评估
a。 -
结果是否为真值?返回它。
-
否则,评估
b并返回结果。
换句话说,以下两个表达式大致等价:
a || b
a ? a : b
示例:
> true || false
true
> true || 'abc'
true
> false || true
true
> false || 'abc'
'abc'
> 'abc' || 'def'
'abc'
17.5.4.1 逻辑或(||)的遗留用例:提供默认值
ECMAScript 2020 引入了空值合并运算符(??)用于默认值。在此之前,逻辑或用于此目的:
const valueToUse = receivedValue || defaultValue;
有关??和在此情况下||的缺点,请参阅“用于默认值的空值合并运算符(??)^(ES2020)”(§16.4)以获取更多信息。
遗留练习:通过或运算符(||)提供默认值
exercises/booleans/default_via_or_exrc.mjs
17.6 逻辑非(!)
表达式!x(“非x”)的评估如下:
-
评估
x。 -
将结果强制转换为布尔值。
-
那个结果是否为
true?返回false。 -
返回
true。
示例:
> !false
true
> !true
false
> !0
true
> !123
false
> !''
true
> !'abc'
false
18 数字
-
18.1 数字用于浮点数和整数
-
18.2 数字字面量
-
18.2.1 整数字面量
-
18.2.2 浮点数字面量
-
18.2.3 语法陷阱:十进制整数字面量的属性
-
18.2.4 下划线(
_)作为数字字面量的分隔符(ES2021)
-
-
18.3 算术运算符
-
18.3.1 二进制算术运算符
-
18.3.2 一元加号(
+)和一元减号(-) -
18.3.3 增量(
++)和减少(--)
-
-
18.4 转换为数字
-
18.5 数字错误值
NaN和Infinity-
18.5.1 错误值:
NaN -
18.5.2 错误值:
Infinity
-
-
18.6 数字的精度:小心处理小数
-
18.7 (高级)
-
18.8 背景:浮点数精度
- 18.8.1 浮点数的简化表示
-
18.9 JavaScript 中的整数数字
-
18.9.1 整数与带分数的浮点数有何不同?
-
18.9.2 转换为整数
-
18.9.3 JavaScript 中整数数字的范围
-
18.9.4 安全整数
-
-
18.10 位运算符(高级)
-
18.10.1 内部位运算符使用 32 位整数
-
18.10.2 位非运算
-
18.10.3 二进制位运算符
-
18.10.4 位运算符
-
18.10.5
b32(): 以二进制表示法显示无符号 32 位整数
-
-
18.11 快速参考:数字
-
18.11.1 数字的全局函数
-
18.11.2
Number.*: 数据属性 -
18.11.3
Number.*: 方法 -
18.11.4
Number.prototype.* -
18.11.5
Number.*: 整数的属性和方法 -
18.11.6 来源
-
JavaScript 有两种数值类型:
-
数字是双精度浮点数 - 根据IEEE 浮点算术标准(IEEE 754)实现的 64 位浮点数。
- 它们也用于范围在±53 位内的较小整数。更多信息,请参阅“JavaScript 中的整数数字”(§18.9)。
-
大整数用任意精度表示整数。
本章涵盖了数字。大整数将在本书的后面部分介绍大整数。
18.1 数字用于浮点数和整数
在 JavaScript 中,类型number用于浮点数和整数:
123.45 // floating point number literal
98 // integer literal
然而,所有数字都是浮点数。整数数字只是没有小数部分的浮点数:
> 98 === 98.0
true
18.2 数字字面量
让我们检查数字字面量。
18.2.1 整数字面量
几个整数字面量让我们可以用不同的基数表示整数:
// Binary (base 2)
assert.equal(0b11, 3); // ES6
// Octal (base 8)
assert.equal(0o10, 8); // ES6
// Decimal (base 10)
assert.equal(35, 35);
// Hexadecimal (base 16)
assert.equal(0xE7, 231);
18.2.2 浮点字面量
浮点数只能用 10 进制表示。
分数:
> 35.0
35
指数:eN表示×10^N
> 3e2
300
> 3e-2
0.03
> 0.3e2
30
18.2.3 语法陷阱:十进制整数字面量的属性
访问十进制整数字面量的属性存在一个陷阱:如果十进制整数字面量后面紧跟着一个点,那么这个点被解释为小数点:
7.toString(); // SyntaxError
有四种方法可以绕过这个陷阱:
(7).toString(2)
7.0.toString(2)
7..toString(2)
7 .toString(2) // space before dot
注意,非十进制整数字面量没有这个陷阱:
> 0b11.toString()
'3'
> 0o11.toString()
'9'
> 0x11.toString()
'17'
18.2.4 数字字面量中的下划线(_)作为分隔符(ES2021)
将数字分组以使长数字更易于阅读有着悠久的历史。例如:
-
1825 年,伦敦有 133.5 万人。
-
地球与太阳之间的距离是 1 亿 4 千 960 万公里。
自从 ES2021 以来,我们可以在数字字面量中使用下划线作为分隔符:
const inhabitantsOfLondon = 1_335_000;
const distanceEarthSunInKm = 149_600_000;
在其他基数中,分组也很重要:
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;
18.2.4.1 分隔符可以放在哪里?
分隔符的位置受到两种方式的限制:
-
我们只能在两个数字之间放置下划线。因此,以下所有数字字面量都是非法的:
3_.141 3._141 1_e12 1e_12 _1464301 // valid variable name! 1464301_ 0_b111111000 0b_111111000 -
我们不能在行内使用多个下划线:
123__456 // two underscores – not allowed
这些限制背后的动机是为了保持解析简单并避免奇怪的边缘情况。
18.2.4.2 带有分隔符的数字解析
以下用于解析数字的函数不支持分隔符:
-
Number() -
Number.parseInt() -
Number.parseFloat()
例如:
> Number('123_456')
NaN
> Number.parseInt('123_456')
123
理由是数字分隔符是为了代码。其他类型的输入应该以不同的方式处理。
18.3 算术运算符
18.3.1 二进制算术运算符
表 18.1 列出了 JavaScript 的二进制算术运算符。
| 运算符 | 名称 | 示例 | |
|---|---|---|---|
n + m |
加法 | ES1 | 3 + 4 → 7 |
n - m |
减法 | ES1 | 9 - 1 → 8 |
n * m |
乘法 | ES1 | 3 * 2.25 → 6.75 |
n / m |
除法 | ES1 | 5.625 / 5 → 1.125 |
n % m |
余数 | ES1 | 8 % 5 → 3 |
-8 % 5 → -3 |
|||
n ** m |
幂运算 | ES2016 | 4 ** 2 → 16 |
表 18.1:二进制算术操作符。
18.3.1.1 % 是余数操作符
% 是余数操作符,而不是取模操作符。其结果具有第一个操作数的符号:
> 5 % 3
2
> -5 % 3
-2
关于余数和取模之间的区别的更多信息,请参阅 2ality 上的博客文章“余数操作符 vs. 取模操作符(带 JavaScript 代码)”“Remainder operator vs. modulo operator (with JavaScript code)”。
18.3.2 一元加号(+)和一元负号(-)
表 18.2 总结了两个操作符一元加号(+)和一元负号(-)。
| 操作符 | 名称 | 示例 | |
|---|---|---|---|
+n |
一元加号 | ES1 | +(-7) → -7 |
-n |
一元负号 | ES1 | -(-7) → 7 |
表 18.2:一元加号(+)和一元负号(-)操作符。
两个操作符都将它们的操作数强制转换为数字:
> +'5'
5
> +'-12'
-12
> -'9'
-9
因此,一元加号允许我们将任意值转换为数字。
18.3.3 增量(++)和减量(--)
增量操作符 ++ 存在前缀版本和后缀版本。在这两种版本中,它都会破坏性地将其操作数增加一。因此,其操作数必须是可以改变的存储位置。
减量操作符 -- 作用相同,但会从其操作数中减去一个。以下两个示例解释了前缀和后缀版本之间的区别。
表 18.3 总结了增量操作符和减量操作符。
| 操作符 | 名称 | 示例 | |
|---|---|---|---|
v++ |
增量 | ES1 | let v=0; [v++, v] → [0, 1] |
++v |
增量 | ES1 | let v=0; [++v, v] → [1, 1] |
v-- |
减量 | ES1 | let v=1; [v--, v] → [1, 0] |
--v |
减量 | ES1 | let v=1; [--v, v] → [0, 0] |
表 18.3:增量操作符和减量操作符。
接下来,我们将查看这些操作符的使用示例。
前缀 ++ 和前缀 -- 改变它们的操作数然后返回它们。
let foo = 3;
assert.equal(++foo, 4);
assert.equal(foo, 4);
let bar = 3;
assert.equal(--bar, 2);
assert.equal(bar, 2);
后缀 ++ 和后缀 -- 返回它们的操作数然后改变它们。
let foo = 3;
assert.equal(foo++, 3);
assert.equal(foo, 4);
let bar = 3;
assert.equal(bar--, 3);
assert.equal(bar, 2);
18.3.3.1 操作数:不仅仅是变量
我们还可以将这些操作符应用于属性值:
const obj = { a: 1 };
++obj.a;
assert.equal(obj.a, 2);
并且应用于数组元素:
const arr = [ 4 ];
arr[0]++;
assert.deepEqual(arr, [5]);
练习:数字操作符
exercises/numbers/is_odd_test.mjs
18.4 转换为数字
这三种方法可以将值转换为数字:
-
Number(value): 有一个描述性的名称,因此推荐使用。表 18.4 总结了其工作方式。 -
+value: 等同于Number(value)。 -
parseFloat(value): 具有异常行为 quirks 并且应该避免使用。
x |
Number(x) |
|---|---|
undefined |
NaN |
null |
0 |
| 布尔值 | false → 0, true → 1 |
| 数字 | x(无变化) |
| 大整数 | -1n → -1, 1n → 1等。 |
| 字符串 | '' → 0 |
| 其他 → 解析的数字,忽略前导/尾随空格 | |
| 符号 | 抛出TypeError |
| 对象 | 可配置的(例如,通过.valueOf()) |
表 18.4:将值转换为数字。
示例:
assert.equal(Number(123.45), 123.45);
assert.equal(Number(''), 0);
assert.equal(Number('\n 123.45 \t'), 123.45);
assert.equal(Number('xyz'), NaN);
assert.equal(Number(-123n), -123);
对象转换为数字的方式可以配置——例如,通过重写.valueOf():
> Number({ valueOf() { return 123 } })
123
练习:转换为数字
exercises/numbers/parse_number_test.mjs
18.5 数值错误值NaN和Infinity
JavaScript 有两个数值错误值:
-
NaN:-
如果解析数字失败或无法执行操作,将返回。
-
通过
Number.isNaN()检测。NaN不严格等于自身。
-
-
Infinity:-
如果一个数字太大或除以零,将返回。
-
通过
Number.isFinite()或通过===比较检测。
-
18.5.1 错误值:NaN
NaN是“不是一个数字”(not a number)的缩写。讽刺的是,JavaScript 将其视为一个数字:
> typeof NaN
'number'
何时返回NaN?
如果一个数字无法解析,将返回NaN:
> Number('$$$')
NaN
> Number(undefined)
NaN
如果操作无法执行,将返回NaN:
> Math.log(-1)
NaN
> Math.sqrt(-1)
NaN
如果操作数或参数是NaN,将返回NaN(以传播错误):
> NaN - 3
NaN
> 7 ** NaN
NaN
18.5.1.1 检查NaN
NaN是唯一一个不严格等于自身的 JavaScript 值:
const n = NaN;
assert.equal(n === n, false);
这些是检查值x是否为NaN的几种方法:
const x = NaN;
assert.equal(Number.isNaN(x), true); // preferred
assert.equal(Object.is(x, NaN), true);
assert.equal(x !== x, true);
在最后一行,我们使用比较怪癖来检测NaN。
18.5.1.2 在数组中查找NaN
一些数组方法无法找到NaN:
> [NaN].indexOf(NaN)
-1
其他方法可以:
> [NaN].includes(NaN)
true
> [NaN].findIndex(x => Number.isNaN(x))
0
> [NaN].find(x => Number.isNaN(x))
NaN
很遗憾,没有简单的经验法则。我们必须检查每个方法如何处理NaN。
18.5.2 错误值:Infinity
何时返回错误值Infinity?
如果一个数字太大,将返回Infinity:
> Math.pow(2, 1023)
8.98846567431158e+307
> Math.pow(2, 1024)
Infinity
> -Math.pow(2, 1024)
-Infinity
如果发生除以零,将返回Infinity:
> 5 / 0
Infinity
> -5 / 0
-Infinity
18.5.2.1 Infinity作为默认值
Infinity大于所有其他数字(除了NaN),因此它是一个很好的默认值:
function findMinimum(numbers) {
let min = Infinity;
for (const n of numbers) {
if (n < min) min = n;
}
return min;
}
assert.equal(findMinimum([5, -1, 2]), -1);
assert.equal(findMinimum([]), Infinity);
这解释了以下结果:
> Math.min()
Infinity
18.5.2.2 检查Infinity
这些是检查值x是否为Infinity的两种常见方式:
const x = Infinity;
assert.equal(x === Infinity, true);
assert.equal(Number.isFinite(x), false);
练习:比较数字
exercises/numbers/find_max_test.mjs
18.6 数字的精度:小心处理小数
内部,JavaScript 浮点数使用 2 为基数(根据 IEEE 754 标准表示)。这意味着十进制小数(10 为基数)不能总是精确表示:
> 0.1 + 0.2
0.30000000000000004
> 1.3 * 3
3.9000000000000004
> 1.4 * 100000000000000
139999999999999.98
因此,在 JavaScript 中进行算术运算时,我们需要考虑舍入误差。
继续阅读以了解这一现象的解释。
18.7 (高级)
本章的所有剩余部分都是高级内容。
18.8 背景信息:浮点精度
在 JavaScript 中,数字的计算并不总是产生正确的结果——例如:
> 0.1 + 0.2
0.30000000000000004
要理解为什么,我们需要探索 JavaScript 内部如何表示浮点数。它使用三个整数来这样做,总共占用 64 位存储空间(双精度):
| 成分 | 大小 | 整数范围 |
|---|---|---|
| 符号 | 1 位 | [0, 1] |
| 分数 | 52 位 | [0, 2⁵²−1] |
| 指数 | 11 位 | [−1023, 1024] |
这些整数表示的浮点数是这样计算的:
(–1)^(符号) × 0b1.尾数 × 2^(指数)
这种表示方法不能编码零,因为它的第二个组件(涉及分数)总是以 1 开头。因此,零通过特殊的指数−1023 和分数 0 进行编码。
18.8.1 浮点数的简化表示
为了使进一步的讨论更容易,我们简化了前面的表示:
-
我们使用 10(十进制)而不是 2(二进制),因为大多数人更熟悉十进制。
-
分数是一个自然数,被解释为分数(小数点后的数字)。我们切换到mantissa,一个被解释为自身的整数。因此,指数的使用方式不同,但其基本作用没有改变。
-
由于尾数是一个整数(具有自己的符号),我们不再需要单独的符号。
新的表示方法如下:
尾数 × 10^(指数)
让我们尝试用几个浮点数来测试这种表示方法。
-
要编码整数 123,我们使用尾数 123 并将其乘以 1(10⁰):
> 123 * (10 ** 0) 123 -
要编码整数−45,我们使用尾数−45,再次使用指数零:
> -45 * (10 ** 0) -45 -
对于数字 1.5,我们想象在尾数后面有一个点。我们使用负指数−1 将那个点向左移动一位:
> 15 * (10 ** -1) 1.5 -
对于数字 0.25,我们将小数点向左移动两位:
> 25 * (10 ** -2) 0.25
换句话说:一旦我们有了小数位,指数就变为负数。我们也可以将这样的数字写成分数的形式:
-
分子(在水平分数线上方):尾数
-
分母(在水平分数线下方):一个指数为正且≥1 的 10。
例如:
> 15 * (10 ** -1) === 15 / (10 ** 1)
true
> 25 * (10 ** -2) === 25 / (10 ** 2)
true
这些分数有助于理解为什么有些数字我们的编码无法表示:
-
1/10可以表示。它已经具有所需的格式:分母中的 10 的幂次。 -
1/2可以表示为5/10。我们将分母中的 2 转换成 10 的幂次,通过将分子和分母都乘以 5 来实现。 -
1/4可以表示为25/100。我们将分母中的 4 转换成 10 的幂次,通过将分子和分母都乘以 25 来实现。 -
1/3不能表示。没有方法可以将分母转换为 10 的幂。(10 的质因数是 2 和 5。因此,任何只有这些质因数的分母可以通过将分子和分母乘以足够的 2 和 5 来转换为 10 的幂。如果一个分母有不同的质因数,那么我们就无能为力了。)
为了结束我们的探索,我们切换回二进制:
-
0.5 = 1/2可以用二进制表示,因为分母已经是 2 的幂。 -
0.25 = 1/4可以用二进制表示,因为分母已经是 2 的幂。 -
0.1 = 1/10不能表示,因为分母不能转换为 2 的幂。 -
0.2 = 2/10不能表示,因为分母不能转换为 2 的幂。
现在我们可以看到为什么 0.1 + 0.2 不会产生正确的结果:内部,两个操作数都无法精确表示。
使用小数精确计算的唯一方法是通过内部切换到十进制。对于许多编程语言,默认是二进制,十进制是选项。例如:
-
Java 有
BigDecimal类BigDecimal。 -
Python 有
decimal模块decimal。
有计划添加类似于 JavaScript 的东西:ECMAScript 提案“十进制”。直到那时,我们可以使用像 big.js 这样的库。
18.9 整数在 JavaScript 中的表示
整数是正常(浮点)数,没有小数部分:
> 1 === 1.0
true
> Number.isInteger(1.0)
true
在本节中,我们将探讨一些用于处理这些伪整数的工具。JavaScript 也支持 大整数,它们是真正的整数。
18.9.1 整数与带分数的浮点数有何不同?
正如我们所见,JavaScript(非大整数)整数只是没有小数部分的浮点数。但它们在以下方面有所不同:
-
在某些位置,只允许整数 – 例如,
Array构造函数只接受整数作为长度:> new Array(1.1) RangeError: Invalid array length > new Array(1.0) [,] -
在某些位置,带分数的数字会被强制转换为不带分数的数字 – 例如,位或(
|)操作将操作数强制转换为 32 位整数:> 3.9 | 0 3 -
JavaScript 有几个常量和操作用于处理整数:
> Math.log2(Number.MAX_SAFE_INTEGER) 53 > Number.isInteger(123.0) true > Number.parseInt('123') 123 -
非十进制整数字面量不能有分数(后缀
.1被解释为读取属性 – 其名称非法以数字开头):0b1.1 // SyntaxError 0o7.1 // SyntaxError 0xF.1 // SyntaxError -
一些 JavaScript 引擎内部以不同的方式表示较小的整数 – 作为真正的整数。例如,V8 对以下“小整数”范围这样做(来源):
-
32 位系统:30 位加上符号位
-
64 位系统:31 位加上符号位
-
18.9.2 转换为整数
将数字转换为整数的推荐方法是使用 Math 对象的舍入方法之一:
-
Math.floor(n): 返回小于等于n的最大整数i> Math.floor(2.1) 2 > Math.floor(2.9) 2 -
Math.ceil(n): 返回大于等于n的最小整数i> Math.ceil(2.1) 3 > Math.ceil(2.9) 3 -
Math.round(n): 返回与n“最接近”的整数,其中.5被向上舍入——例如:> Math.round(2.4) 2 > Math.round(2.5) 3 -
Math.trunc(n): 移除n中的任何小数部分(小数点后),因此将其转换为整数。> Math.trunc(2.1) 2 > Math.trunc(2.9) 2
更多关于四舍五入的信息,请参阅“四舍五入”(§19.3)。
18.9.3 JavaScript 中整数数字的范围
这些是 JavaScript 中整数数字的重要范围:
-
安全整数:可以安全地由 JavaScript 表示(关于这意味着什么将在下一小节中详细介绍)
-
精度:53 位加符号
-
范围:(-2⁵³, 2⁵³)
-
-
数组索引
-
精度:32 位,无符号
-
范围:0, 2³²-1)(不包括最大长度)
-
类型化数组有更大的范围,53 位(安全且无符号)
-
-
位运算符(位或等)
-
精度:32 位
-
无符号右移运算符(
>>>)的范围:无符号,[0, 2³²) -
所有其他位运算符的范围:有符号,[-2³¹, 2³¹)
-
[18.9.4 安全整数
这是 JavaScript 中整数数字的安全范围(53 位加符号):
[–(2⁵³)+1, 2⁵³-1]
一个整数如果是精确地由一个 JavaScript 数字表示,则它是安全的。鉴于 JavaScript 数字是以分数形式乘以 2 的指数编码的,因此也可以表示更高的整数,但它们之间会有间隔。
例如(18014398509481984 是 2⁵⁴):
> 18014398509481983
18014398509481984
> 18014398509481984
18014398509481984
> 18014398509481985
18014398509481984
> 18014398509481986
18014398509481984
> 18014398509481987
18014398509481988
因此,以下数学整数是不安全的:
-
数学整数 18014398509481984 是由以下 JavaScript 数字表示的:
-
18014398509481983
-
18014398509481984
-
18014398509481985
-
18014398509481986
-
-
数学整数 18014398509481985 不能由任何 JavaScript 数字表示。
以下 Number 对象的属性有助于确定一个整数是否安全:
assert.equal(Number.MAX_SAFE_INTEGER, (2 ** 53) - 1);
assert.equal(Number.MIN_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER);
assert.equal(Number.isSafeInteger(5), true);
assert.equal(Number.isSafeInteger('5'), false);
assert.equal(Number.isSafeInteger(5.1), false);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER), true);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER+1), false);
练习:检测安全整数
exercises/numbers/is_safe_integer_test.mjs
18.9.4.1 安全计算
让我们看看涉及不安全整数的计算。
以下结果是不正确的且不安全的,尽管它的两个操作数都是安全的:
> 9007199254740990 + 3
9007199254740992
以下结果是安全的,但是不正确的。第一个操作数是不安全的;第二个操作数是安全的:
> 9007199254740995 - 10
9007199254740986
因此,表达式 a op b 的结果是正确的,当且仅当:
isSafeInteger(a) && isSafeInteger(b) && isSafeInteger(a op b)
即,操作数和结果都必须是安全的。
18.10 位运算符(高级)
18.10.1 内部位运算符使用 32 位整数
内部,JavaScript 的位运算符使用 32 位整数进行操作。它们按照以下步骤产生结果:
-
输入(JavaScript 数字):1-2 个操作数首先被转换为 JavaScript 数字(64 位浮点数),然后转换为 32 位整数。
-
计算(32 位整数):实际操作处理 32 位整数并产生一个 32 位整数。
-
输出(JavaScript 数字):在返回结果之前,它被转换回 JavaScript 数字。
18.10.1.1 操作数和结果的类型
对于每个按位运算符,本书都提到了其操作数的类型及其结果。每个类型始终是以下两种之一:
| 类型 | 描述 | 大小 | 范围 |
|---|---|---|---|
| Int32 | 有符号 32 位整数 | 32 位,包括符号 | −2³¹, 2³¹) |
| Uint32 | 无符号 32 位整数 | 32 位 | [0, 2³²) |
考虑到前面提到的步骤,我建议假设按位运算符在内部以无符号 32 位整数(计算步骤)工作,而 Int32 和 Uint32 只影响 JavaScript 数字转换为整数以及从整数转换回 JavaScript 数字的方式(输入和输出步骤)。
[18.10.1.2 显示 JavaScript 数字为无符号 32 位整数
在探索按位运算符时,有时将 JavaScript 数字以二进制形式显示为无符号 32 位整数会有所帮助。这正是 b32() 所做的(其实现将在后面展示):
assert.equal(
b32(-1),
'11111111111111111111111111111111');
assert.equal(
b32(1),
'00000000000000000000000000000001');
assert.equal(
b32(2 ** 31),
'10000000000000000000000000000000');
18.10.2 按位非
| 操作 | 名称 | 类型签名 | |
|---|---|---|---|
~num |
按位非,补码 | Int32 → Int32 | ES1 |
表 18.5:按位非运算符。
按位非运算符(表 18.5)反转其操作数的每个二进制位:
> b32(~0b100)
'11111111111111111111111111111011'
这种所谓的 补码 对于某些算术操作类似于负数。例如,将一个整数与其补码相加总是 -1:
> 4 + ~4
-1
> -11 + ~-11
-1
18.10.3 二进制按位运算符
| 操作 | 名称 | 类型签名 | |
|---|---|---|---|
num1 & num2 |
按位与 | Int32 × Int32 → Int32 | ES1 |
num1 ¦ num2 |
按位或 | Int32 × Int32 → Int32 | ES1 |
num1 ^ num2 |
按位异或 | Int32 × Int32 → Int32 | ES1 |
表 18.6:二进制按位运算符。
二进制按位运算符(表 18.6)将它们的操作数的位组合起来以产生结果:
> (0b1010 & 0b0011).toString(2).padStart(4, '0')
'0010'
> (0b1010 | 0b0011).toString(2).padStart(4, '0')
'1011'
> (0b1010 ^ 0b0011).toString(2).padStart(4, '0')
'1001'
18.10.4 按位移位运算符
| 操作 | 名称 | 类型签名 | |
|---|---|---|---|
num << count |
左移 | Int32 × Uint32 → Int32 | ES1 |
num >> count |
有符号右移 | Int32 × Uint32 → Int32 | ES1 |
num >>> count |
无符号右移 | Uint32 × Uint32 → Uint32 | ES1 |
表 18.7:按位移位运算符。
移位运算符(表 18.7)将二进制位向左或向右移动:
> (0b10 << 1).toString(2)
'100'
>> 保留最高位,>>> 不保留:
> b32(0b10000000000000000000000000000010 >> 1)
'11000000000000000000000000000001'
> b32(0b10000000000000000000000000000010 >>> 1)
'01000000000000000000000000000001'
18.10.5 b32():以二进制形式显示无符号 32 位整数
我们已经多次使用了 b32()。以下代码是它的实现:
/**
* Return a string representing n as a 32-bit unsigned integer,
* in binary notation.
*/
function b32(n) {
// >>> ensures highest bit isn’t interpreted as a sign
return (n >>> 0).toString(2).padStart(32, '0');
}
assert.equal(
b32(6),
'00000000000000000000000000000110');
n >>> 0 表示我们将 n 向右移动 0 位。因此,原则上,>>> 运算符什么都不做,但它仍然将 n 强制转换为无符号 32 位整数:
> 12 >>> 0
12
> -12 >>> 0
4294967284
> (2**32 + 1) >>> 0
1
18.11 快速参考:数字
18.11.1 数字的全局函数
JavaScript 有以下四个用于数字的全局函数:
-
isFinite() -
isNaN() -
parseFloat() -
parseInt()
然而,最好使用 Number 的相应方法(如 Number.isFinite() 等),这些方法有更少的陷阱。它们是在 ES6 中引入的,下面将讨论。
18.11.2 Number.*: 数据属性
-
Number.EPSILONES61 和下一个可表示的浮点数之间的差异。一般来说,机器精度为浮点数算术中的舍入误差提供了一个上限。
- 大约:2.2204460492503130808472633361816 × 10^(-16)
-
Number.MAX_VALUEES1最大的正有限 JavaScript 数字。
- 大约:1.7976931348623157 × 10³⁰⁸
-
Number.MIN_VALUEES1最小的正 JavaScript 数字。大约 5 × 10^(−324)。
-
Number.NaNES1与全局变量
NaN相同。 -
Number.NEGATIVE_INFINITYES1与
-Number.POSITIVE_INFINITY相同。 -
Number.POSITIVE_INFINITYES1与全局变量
Infinity相同。
18.11.3 Number.*: 方法
-
Number.isFinite(num)ES6如果
num是一个实际的数字(既不是Infinity也不是-Infinity也不是NaN),则返回true。> Number.isFinite(Infinity) false > Number.isFinite(-Infinity) false > Number.isFinite(NaN) false > Number.isFinite(123) true -
Number.isNaN(num)ES6如果
num的值是NaN,则返回true:> Number.isNaN(NaN) true > Number.isNaN(123) false > Number.isNaN('abc') false -
Number.parseFloat(str)ES6将其参数强制转换为字符串,并将其解析为浮点数。它忽略前导空白和非法尾随字符:
> Number.parseFloat('\t 123.4#') 123.4这可能会隐藏问题。因此,对于将字符串转换为数字,
Number()通常是一个更好的选择,因为它只忽略前导和尾随空白:> Number('\t 123.4#') NaN
18.11.4 Number.prototype.*
(Number.prototype 是存储数字方法的区域。)
-
Number.prototype.toExponential(fractionDigits?)ES3-
返回一个表示数字的字符串,通过指数表示法。
-
通过
fractionDigits,我们可以指定乘以指数的数字应该显示多少位数字。- 默认情况下,显示所需的所有数字。
示例:数字太小,无法通过
.toString()获取正指数。> 1234..toString() '1234' > 1234..toExponential() // 3 fraction digits '1.234e+3' > 1234..toExponential(5) '1.23400e+3' > 1234..toExponential(1) '1.2e+3'示例:分数不够小,无法通过
.toString()获取负指数。> 0.003.toString() '0.003' > 0.003.toExponential() '3e-3' -
-
Number.prototype.toFixed(fractionDigits=0)ES3返回一个不带指数的数字字符串表示形式,四舍五入到
fractionDigits位数字。> 0.00000012.toString() // with exponent '1.2e-7' > 0.00000012.toFixed(10) // no exponent '0.0000001200' > 0.00000012.toFixed() '0'如果数字是 10²¹ 或更大,即使是
.toFixed()也使用指数:> (10 ** 21).toFixed() '1e+21' -
Number.prototype.toPrecision(precision?)ES3-
与
.toString()类似,但precision指定了总共应该显示多少位数字。 -
如果缺少
precision,则使用.toString()。
> 1234..toPrecision(3) // requires exponential notation '1.23e+3' > 1234..toPrecision(4) '1234' > 1234..toPrecision(5) '1234.0' > 1.234.toPrecision(3) '1.23' -
-
Number.prototype.toString(radix=10)ES1返回数字的字符串表示形式。
默认情况下,我们得到一个基数为 10 的数字:
> 123.456.toString() '123.456'如果我们想使数字有不同的基数,我们可以通过
radix指定它:> 4..toString(2) // binary (base 2) '100' > 4.5.toString(2) '100.1' > 255..toString(16) // hexadecimal (base 16) 'ff' > 255.66796875.toString(16) 'ff.ab' > 1234567890..toString(36) 'kf12oi'Number.parseInt()提供逆操作:它将包含整数字符(没有分数!)的字符串(给定基数)转换为数字。> Number.parseInt('kf12oi', 36) 1234567890
18.11.5 Number.*:整数的数据属性和方法
-
Number.MIN_SAFE_INTEGERES6JavaScript 可以无歧义表示的最小整数(-2⁵³+1)。
-
Number.MAX_SAFE_INTEGERES6JavaScript 可以无歧义表示的最大整数(2⁵³−1)。
-
Number.isInteger(num)ES6如果
num是一个数字并且没有小数部分,则返回true。> Number.isInteger(-17) true > Number.isInteger(33) true > Number.isInteger(33.1) false > Number.isInteger('33') false > Number.isInteger(NaN) false > Number.isInteger(Infinity) false -
Number.isSafeInteger(num)ES6如果
num是一个数字并且无歧义地表示一个整数,则返回true。 -
Number.parseInt(str, radix=10)ES6将其参数强制转换为字符串并将其解析为整数,忽略前导空白和非法尾随字符:
> Number.parseInt(' 123#') 123参数
radix指定要解析的数字的基数:> Number.parseInt('101', 2) 5 > Number.parseInt('FF', 16) 255不要使用这种方法将数字转换为整数:将数字强制转换为字符串是不高效的。并且在第一个非数字字符之前停止并不是移除数字分数的好算法。以下是一个出错示例:
> Number.parseInt(1e21, 10) // wrong 1使用
Math的其中一个舍入函数将数字转换为整数更好:> Math.trunc(1e21) // correct 1e+21
18.11.6 源
19 Math
-
19.1 数据属性
-
19.2 指数、根、对数
-
19.3 舍入
-
19.3.1 舍入到整数
-
19.3.2 舍入浮点数到 32 位和 16 位
-
-
19.4 三角函数
-
19.5 其他各种函数
-
19.6 来源
Math 是一个具有数据属性和数字处理方法的对象。你可以将其视为一个穷人的模块:它是在 JavaScript 有了模块之前创建的。
19.1 数据属性
-
Math.E: numberES1欧拉数,自然对数的底数,大约为 2.7182818284590452354。
-
Math.LN10: numberES110 的自然对数,大约为 2.302585092994046。
-
Math.LN2: numberES12 的自然对数,大约为 0.6931471805599453。
-
Math.LOG10E: numberES1以 10 为底的对数 e,大约为 0.4342944819032518。
-
Math.LOG2E: numberES1以 2 为底的对数 e,大约为 1.4426950408889634。
-
Math.PI: numberES1数学常数 π,圆周与直径的比值,大约为 3.1415926535897932。
-
Math.SQRT1_2: numberES11/2 的平方根,大约为 0.7071067811865476。
-
Math.SQRT2: numberES12 的平方根,大约为 1.4142135623730951。
19.2 指数、根、对数
-
Math.cbrt(x: number): numberES6返回
x的立方根。> Math.cbrt(8) 2 -
Math.exp(x: number): numberES1返回 e^(
x)(e 为欧拉数)。Math.log()的逆函数。> Math.exp(0) 1 > Math.exp(1) === Math.E true -
Math.expm1(x: number): numberES6返回
Math.exp(x)-1。Math.log1p()的逆函数。非常小的数字(接近 0 的分数)以更高的精度表示。因此,当.exp()返回接近 1 的值时,此函数返回更精确的值。 -
Math.log(x: number): numberES1返回
x的自然对数(以 e 为底,即欧拉数)。Math.exp()的逆函数。> Math.log(1) 0 > Math.log(Math.E) 1 > Math.log(Math.E ** 2) 2 -
Math.log1p(x: number): numberES6返回
Math.log(1 + x)。Math.expm1()的逆函数。非常小的数字(接近 0 的分数)以更高的精度表示。因此,当.log()的参数接近 1 时,你可以向此函数提供更精确的参数。 -
Math.log10(x: number): numberES6返回以 10 为底的对数
x。10 ** x的逆函数。> Math.log10(1) 0 > Math.log10(10) 1 > Math.log10(100) 2 -
Math.log2(x: number): numberES6返回以 2 为底的对数
x。2 ** x的逆函数。> Math.log2(1) 0 > Math.log2(2) 1 > Math.log2(4) 2 -
Math.pow(x: number, y: number): numberES1返回
x^(y),x的y次幂。等同于x ** y。> Math.pow(2, 3) 8 > Math.pow(25, 0.5) 5 -
Math.sqrt(x: number): numberES1返回
x的平方根。x ** 2的逆函数。> Math.sqrt(9) 3
19.3 舍入
19.3.1 舍入到整数
舍入到整数意味着将任意数转换为整数(一个没有小数部分的数)。以下函数实现了多种实现此目的的方法。
-
Math.ceil(x: number): numberES1返回最小的(最接近-∞)整数
i,使得x≤i。> Math.ceil(2.1) 3 > Math.ceil(2.9) 3 -
Math.floor(x: number): numberES1返回最大的(最接近+∞)整数
i,使得i≤x。> Math.floor(2.1) 2 > Math.floor(2.9) 2 -
Math.round(x: number): numberES1返回最接近
x的整数。如果x的小数部分是.5,则.round()向上舍入(到更接近正无穷大的整数):> Math.round(2.4) 2 > Math.round(2.5) 3 -
Math.trunc(x: number): numberES6移除
x的小数部分并返回结果整数。> Math.trunc(2.1) 2 > Math.trunc(2.9) 2
表 19.1 显示了几个代表性输入的舍入函数的结果。
-2.9 |
-2.5 |
-2.1 |
2.1 |
2.5 |
2.9 |
|
|---|---|---|---|---|---|---|
Math.floor |
-3 |
-3 |
-3 |
2 |
2 |
2 |
Math.ceil |
-2 |
-2 |
-2 |
3 |
3 |
3 |
Math.round |
-3 |
-2 |
-2 |
2 |
3 |
3 |
Math.trunc |
-2 |
-2 |
-2 |
2 |
2 |
2 |
表 19.1: Math的舍入函数。注意负数时情况的变化,因为“更大”总是意味着“更接近正无穷大”。
19.3.2 将浮点数舍入到 32 位和 16 位
-
Math.fround(x: number): numberES6将
x舍入到 32 位单浮点数(在 64 位双精度浮点数内):> Math.fround(2**128) Infinity > 2**128 3.402823669209385e+38 > Math.fround(2**-150) 0 > 2**-150 7.006492321624085e-46 -
Math.f16round(x: number): numberES2025将
x舍入到 16 位半浮点数(在 64 位双精度浮点数内):> Math.f16round(2**16) Infinity > 2**16 65536 > Math.f16round(2**-25) 0 > 2**-25 2.9802322387695312e-8
19.3.2.1 处理溢出和下溢
Math.f16round(x)将x舍入到 16 位半浮点数(在 64 位双精度浮点数内)。
如果存在正溢出(正数离零太远),则结果为正无穷大:
> Math.f16round(2**15)
32768
> Math.f16round(2**16)
Infinity
> 2**16
65536
如果存在负溢出(负数离零太远),则结果为负无穷大:
> Math.f16round(-(2**15))
-32768
> Math.f16round(-(2**16))
-Infinity
> -(2**16)
-65536
算术下溢意味着一个数在二进制小数点后有太多数字(太接近整数)。如果发生这种情况,无法表示的数字将被省略:
> Math.f16round(2**-24)
5.960464477539063e-8
> Math.f16round(2**-25)
0
> 2**-25
2.9802322387695312e-8
19.4 三角函数
所有角度均以弧度为单位。使用以下两个函数在度与弧度之间进行转换。
function degreesToRadians(degrees) {
return degrees / 180 * Math.PI;
}
assert.equal(degreesToRadians(90), Math.PI/2);
function radiansToDegrees(radians) {
return radians / Math.PI * 180;
}
assert.equal(radiansToDegrees(Math.PI), 180);
-
Math.acos(x: number): numberES1返回
x的反正切(反余弦)值。> Math.acos(0) 1.5707963267948966 > Math.acos(1) 0 -
Math.acosh(x: number): numberES6返回
x的反双曲余弦值。 -
Math.asin(x: number): numberES1返回
x的正弦值。> Math.asin(0) 0 > Math.asin(1) 1.5707963267948966 -
Math.asinh(x: number): numberES6返回
x的反双曲正弦值。 -
Math.atan(x: number): numberES1返回
x的反正切(反切)值。 -
Math.atanh(x: number): numberES6返回
x的反双曲正切值。 -
Math.atan2(y: number, x: number): numberES1返回
y/x的商的反正切。 -
Math.cos(x: number): numberES1返回
x的余弦值。> Math.cos(0) 1 > Math.cos(Math.PI) -1 -
Math.cosh(x: number): numberES6返回
x的双曲余弦值。 -
Math.hypot(...values: Array<number>): numberES6返回
values的平方和的平方根(毕达哥拉斯定理):> Math.hypot(3, 4) 5 -
Math.sin(x: number): numberES1返回
x的正弦。> Math.sin(0) 0 > Math.sin(Math.PI / 2) 1 -
Math.sinh(x: number): numberES6返回
x的双曲正弦。 -
Math.tan(x: number): numberES1返回
x的正切。> Math.tan(0) 0 > Math.tan(1) 1.5574077246549023 -
Math.tanh(x: number): number;ES6返回
x的双曲正切。
19.5 其他各种函数
-
Math.abs(x: number): numberES1返回
x的绝对值。> Math.abs(3) 3 > Math.abs(-3) 3 > Math.abs(0) 0 -
Math.clz32(x: number): numberES6计算在 32 位整数
x中的前导零位。用于数字信号处理算法。> Math.clz32(0b01000000000000000000000000000000) 1 > Math.clz32(0b00100000000000000000000000000000) 2 > Math.clz32(2) 30 > Math.clz32(1) 31 -
Math.max(...values: Array<number>): numberES1将
values转换为数字并返回最大值。> Math.max(3, -5, 24) 24 -
Math.min(...values: Array<number>): numberES1将
values转换为数字并返回最小值。> Math.min(3, -5, 24) -5 -
Math.random(): numberES1返回一个伪随机数
n,其中 0 ≤n< 1。/** Returns a random integer i with 0 <= i < max */ function getRandomInteger(max) { return Math.floor(Math.random() * max); } -
Math.sign(x: number): numberES6返回一个数字的符号:
> Math.sign(-8) -1 > Math.sign(0) 0 > Math.sign(3) 1
19.6 源


浙公网安备 33010602011771号