探索-JavaScript-ES2025-版--三-

探索 JavaScript(ES2025 版)(三)

原文:exploringjs.com/js/book/index.html

译者:飞龙

协议:CC BY-NC-SA 4.0

IV 原始值

原文:exploringjs.com/js/book/pt_primitive-values.html

16 非值 undefinednull

原文:exploringjs.com/js/book/ch_undefined-null.html

  1. 16.1 undefinednull 的比较

  2. 16.2 undefinednull 的出现情况

    1. 16.2.1 undefined 的出现情况

    2. 16.2.2 null 的出现情况

  3. 16.3 检查 undefinednull

  4. 16.4 用于默认值的空值合并运算符 (??) (ES2020)

    1. 16.4.0.1 ?? 是短路操作

    2. 16.4.1 示例:计算匹配项

    3. 16.4.2 示例:为属性指定默认值

    4. 16.4.3 传统方法:使用逻辑或 (||) 设置默认值

    5. 16.4.4 空值合并赋值运算符 (??=) (ES2021)

  5. 16.5 undefinednull 没有属性

  6. 16.6 undefinednull 的历史

许多编程语言都有一个名为 null 的“非值”。它表示变量当前不指向对象——例如,当它尚未初始化时。

相比之下,JavaScript 有两个这样的值:undefinednull

16.1 undefinednull 的比较

这两个值非常相似,通常可以互换使用。它们之间的区别因此很微妙。语言本身做出了以下区分:

  • undefined 表示“未初始化”(例如,一个变量)或“不存在”(例如,一个对象的属性)。

  • null 表示“任何对象值的故意缺失”(来自 语言规范 的引用)。

程序员有时会做出以下区分:

  • undefined 是语言中使用的非值(当某物未初始化时等)。

  • null 表示“显式关闭”。也就是说,它有助于实现一种包含有意义的值和表示“无有意义的值”的元值的类型。这种类型在函数式编程中称为 选项类型可能类型

16.2 undefinednull 的出现情况

以下小节描述了 undefinednull 在语言中的出现位置。我们将在本书的后续部分更详细地解释几个机制。

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 的出现情况

对象的原型要么是一个对象,要么是原型链末尾的 nullObject.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 检查 undefinednull

检查以下任一:

if (x === null) ···
if (x === undefined) ···

x 有值吗?

if (x !== undefined && x !== null) {
  // ···
}
if (x) { // truthy?
  // x is neither: undefined, null, false, 0, NaN, 0n, ''
}

xundefinednull 吗?

if (x === undefined || x === null) {
  // ···
}
if (!x) { // falsy?
  // x is: undefined, null, false, 0, NaN, 0n, ''
}

真值意味着“如果转换为布尔值则为 true”。假值意味着“如果转换为布尔值则为 false”。这两个概念在“假值和真值” (§17.2) 中得到了适当的解释。

16.4 空值合并运算符 (??) 用于默认值 (ES2020)

空值合并运算符(??)允许我们在值是 undefinednull 时使用默认值:

value ?? defaultValue

  • 如果 value 不是 undefinednull,则评估 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
);

如果 regexstr 内有一个或多个匹配项,则 .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 和空值合并运算符之前,逻辑或用于默认值。这有一个缺点。

||undefinednull 的处理是预期的:

> 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)

空值合并赋值运算符(??=)在值是 undefinednull 时分配一个默认值:

value ??= defaultValue

  • 如果 valueundefinednull,则评估 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)

这意味着 ??= 是短路操作 – 只有当 aundefinednull 时,以下两个操作才会发生:

  • 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 undefinednull 没有属性

undefinednull 是 JavaScript 中唯一两个尝试读取属性时会抛出异常的值。为了探索这一现象,让我们使用以下函数,该函数读取(获取)属性 .prop 并返回结果。

function getProp(x) {
  return x.prop;
}

如果我们将 getProp() 应用到各种值上,我们可以看到它只对 undefinednull 失败:

> 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 undefinednull 的历史

在 Java(它启发了 JavaScript 的许多方面)中,初始化值取决于变量的静态类型:

  • 对象类型的变量使用 null 进行初始化。

  • 每个原始类型都有自己的初始化值。例如,int 类型的变量初始化为 0

JavaScript 借用了 null 并在期望对象的地方使用它。它意味着“不是一个对象”。

然而,JavaScript 中的存储位置(变量、属性等)可以存储原始值或对象。它们需要一个表示“既不是对象也不是原始值”的初始化值。这就是为什么引入了 undefined 的原因。

17 布尔值

原文:exploringjs.com/js/book/ch_booleans.html

  1. 17.1 转换为布尔值

  2. 17.2 假值和真值

    1. 17.2.1 检查真值或假值
  3. 17.3 基于真值的检查

    1. 17.3.1 陷阱:基于真值的存在检查是不精确的

    2. 17.3.2 用例:是否提供了参数?

    3. 17.3.3 用例:属性是否存在?

  4. 17.4 条件运算符(? :)

  5. 17.5 二元逻辑运算符:与(x && y),或(x || y)

    1. 17.5.1 值保留

    2. 17.5.2 短路

    3. 17.5.3 逻辑与 (x && y)

    4. 17.5.4 逻辑或 (||)

  6. 17.6 逻辑非(!)

原始类型布尔包含两个值——falsetrue

> 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(无变化)
数字 0falseNaNfalse
其他数字 → true
大整数 0false
其他数字 → 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

  • 数字:0NaN

  • 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.propundefined

  • obj.prop是任何其他假值(null0''等)。

在实践中,这很少引起问题,但我们必须意识到这个陷阱。

17.3.2 用例:参数是否提供?

真值检查常用于确定函数的调用者是否提供了参数:

function func(x) {
  if (!x) {
    throw new Error('Missing parameter x');
  }
  // ···
}

优点在于,这种模式已经建立并且简短。它正确地抛出undefinednull的错误。

缺点在于,之前提到的陷阱:代码也会为所有其他假值抛出错误。

另一个选择是检查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(“ab”)的评估如下:

  1. 评估a

  2. 结果是否为假值?返回它。

  3. 否则,评估b并返回结果。

换句话说,以下两个表达式大致等价:

a && b
!a ? a : b

示例:

> false && true
false
> false && 'abc'
false

> true && false
false
> true && 'abc'
'abc'

> '' && 'abc'
''

17.5.4 逻辑或(||

表达式a || b(“ab”)的评估如下:

  1. 评估a

  2. 结果是否为真值?返回它。

  3. 否则,评估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”)的评估如下:

  1. 评估x

  2. 将结果强制转换为布尔值。

  3. 那个结果是否为true?返回false

  4. 返回true

示例:

> !false
true
> !true
false

> !0
true
> !123
false

> !''
true
> !'abc'
false

18 数字

原文:exploringjs.com/js/book/ch_numbers.html

  1. 18.1 数字用于浮点数和整数

  2. 18.2 数字字面量

    1. 18.2.1 整数字面量

    2. 18.2.2 浮点数字面量

    3. 18.2.3 语法陷阱:十进制整数字面量的属性

    4. 18.2.4 下划线(_)作为数字字面量的分隔符(ES2021)

  3. 18.3 算术运算符

    1. 18.3.1 二进制算术运算符

    2. 18.3.2 一元加号(+)和一元减号(-

    3. 18.3.3 增量(++)和减少(--

  4. 18.4 转换为数字

  5. 18.5 数字错误值 NaNInfinity

    1. 18.5.1 错误值:NaN

    2. 18.5.2 错误值:Infinity

  6. 18.6 数字的精度:小心处理小数

  7. 18.7 (高级)

  8. 18.8 背景:浮点数精度

    1. 18.8.1 浮点数的简化表示
  9. 18.9 JavaScript 中的整数数字

    1. 18.9.1 整数与带分数的浮点数有何不同?

    2. 18.9.2 转换为整数

    3. 18.9.3 JavaScript 中整数数字的范围

    4. 18.9.4 安全整数

  10. 18.10 位运算符(高级)

    1. 18.10.1 内部位运算符使用 32 位整数

    2. 18.10.2 位非运算

    3. 18.10.3 二进制位运算符

    4. 18.10.4 位运算符

    5. 18.10.5 b32(): 以二进制表示法显示无符号 32 位整数

  11. 18.11 快速参考:数字

    1. 18.11.1 数字的全局函数

    2. 18.11.2 Number.*: 数据属性

    3. 18.11.3 Number.*: 方法

    4. 18.11.4 Number.prototype.*

    5. 18.11.5 Number.*: 整数的属性和方法

    6. 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 + 47
n - m 减法 ES1 9 - 18
n * m 乘法 ES1 3 * 2.256.75
n / m 除法 ES1 5.625 / 51.125
n % m 余数 ES1 8 % 53
-8 % 5-3
n ** m 幂运算 ES2016 4 ** 216

表 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
布尔值 false0, true1
数字 x(无变化)
大整数 -1n-1, 1n1等。
字符串 ''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 数值错误值NaNInfinity

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 不会产生正确的结果:内部,两个操作数都无法精确表示。

使用小数精确计算的唯一方法是通过内部切换到十进制。对于许多编程语言,默认是二进制,十进制是选项。例如:

有计划添加类似于 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.EPSILON ES6

    1 和下一个可表示的浮点数之间的差异。一般来说,机器精度为浮点数算术中的舍入误差提供了一个上限。

    • 大约:2.2204460492503130808472633361816 × 10^(-16)
  • Number.MAX_VALUE ES1

    最大的正有限 JavaScript 数字。

    • 大约:1.7976931348623157 × 10³⁰⁸
  • Number.MIN_VALUE ES1

    最小的正 JavaScript 数字。大约 5 × 10^(−324)。

  • Number.NaN ES1

    与全局变量 NaN 相同。

  • Number.NEGATIVE_INFINITY ES1

    -Number.POSITIVE_INFINITY 相同。

  • Number.POSITIVE_INFINITY ES1

    与全局变量 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_INTEGER ES6

    JavaScript 可以无歧义表示的最小整数(-2⁵³+1)。

  • Number.MAX_SAFE_INTEGER ES6

    JavaScript 可以无歧义表示的最大整数(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

原文:exploringjs.com/js/book/ch_math.html

  1. 19.1 数据属性

  2. 19.2 指数、根、对数

  3. 19.3 舍入

    1. 19.3.1 舍入到整数

    2. 19.3.2 舍入浮点数到 32 位和 16 位

  4. 19.4 三角函数

  5. 19.5 其他各种函数

  6. 19.6 来源

Math 是一个具有数据属性和数字处理方法的对象。你可以将其视为一个穷人的模块:它是在 JavaScript 有了模块之前创建的。

19.1 数据属性

  • Math.E: number ES1

    欧拉数,自然对数的底数,大约为 2.7182818284590452354。

  • Math.LN10: number ES1

    10 的自然对数,大约为 2.302585092994046。

  • Math.LN2: number ES1

    2 的自然对数,大约为 0.6931471805599453。

  • Math.LOG10E: number ES1

    以 10 为底的对数 e,大约为 0.4342944819032518。

  • Math.LOG2E: number ES1

    以 2 为底的对数 e,大约为 1.4426950408889634。

  • Math.PI: number ES1

    数学常数 π,圆周与直径的比值,大约为 3.1415926535897932。

  • Math.SQRT1_2: number ES1

    1/2 的平方根,大约为 0.7071067811865476。

  • Math.SQRT2: number ES1

    2 的平方根,大约为 1.4142135623730951。

19.2 指数、根、对数

  • Math.cbrt(x: number): number ES6

    返回 x 的立方根。

    > Math.cbrt(8)
    2
    
    
  • Math.exp(x: number): number ES1

    返回 e^(x)(e 为欧拉数)。Math.log() 的逆函数。

    > Math.exp(0)
    1
    > Math.exp(1) === Math.E
    true
    
    
  • Math.expm1(x: number): number ES6

    返回 Math.exp(x)-1Math.log1p() 的逆函数。非常小的数字(接近 0 的分数)以更高的精度表示。因此,当 .exp() 返回接近 1 的值时,此函数返回更精确的值。

  • Math.log(x: number): number ES1

    返回 x 的自然对数(以 e 为底,即欧拉数)。Math.exp() 的逆函数。

    > Math.log(1)
    0
    > Math.log(Math.E)
    1
    > Math.log(Math.E ** 2)
    2
    
    
  • Math.log1p(x: number): number ES6

    返回 Math.log(1 + x)Math.expm1() 的逆函数。非常小的数字(接近 0 的分数)以更高的精度表示。因此,当 .log() 的参数接近 1 时,你可以向此函数提供更精确的参数。

  • Math.log10(x: number): number ES6

    返回以 10 为底的对数 x10 ** x 的逆函数。

    > Math.log10(1)
    0
    > Math.log10(10)
    1
    > Math.log10(100)
    2
    
    
  • Math.log2(x: number): number ES6

    返回以 2 为底的对数 x2 ** x 的逆函数。

    > Math.log2(1)
    0
    > Math.log2(2)
    1
    > Math.log2(4)
    2
    
    
  • Math.pow(x: number, y: number): number ES1

    返回 x^(y),xy 次幂。等同于 x ** y

    > Math.pow(2, 3)
    8
    > Math.pow(25, 0.5)
    5
    
    
  • Math.sqrt(x: number): number ES1

    返回 x 的平方根。x ** 2 的逆函数。

    > Math.sqrt(9)
    3
    
    

19.3 舍入

19.3.1 舍入到整数

舍入到整数意味着将任意数转换为整数(一个没有小数部分的数)。以下函数实现了多种实现此目的的方法。

  • Math.ceil(x: number): number ES1

    返回最小的(最接近-∞)整数i,使得xi

    > Math.ceil(2.1)
    3
    > Math.ceil(2.9)
    3
    
    
  • Math.floor(x: number): number ES1

    返回最大的(最接近+∞)整数i,使得ix

    > Math.floor(2.1)
    2
    > Math.floor(2.9)
    2
    
    
  • Math.round(x: number): number ES1

    返回最接近x的整数。如果x的小数部分是.5,则.round()向上舍入(到更接近正无穷大的整数):

    > Math.round(2.4)
    2
    > Math.round(2.5)
    3
    
    
  • Math.trunc(x: number): number ES6

    移除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): number ES6

    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): number ES2025

    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): number ES1

    返回x的反正切(反余弦)值。

    > Math.acos(0)
    1.5707963267948966
    > Math.acos(1)
    0
    
    
  • Math.acosh(x: number): number ES6

    返回x的反双曲余弦值。

  • Math.asin(x: number): number ES1

    返回x的正弦值。

    > Math.asin(0)
    0
    > Math.asin(1)
    1.5707963267948966
    
    
  • Math.asinh(x: number): number ES6

    返回x的反双曲正弦值。

  • Math.atan(x: number): number ES1

    返回x的反正切(反切)值。

  • Math.atanh(x: number): number ES6

    返回x的反双曲正切值。

  • Math.atan2(y: number, x: number): number ES1

    返回y/x的商的反正切。

  • Math.cos(x: number): number ES1

    返回x的余弦值。

    > Math.cos(0)
    1
    > Math.cos(Math.PI)
    -1
    
    
  • Math.cosh(x: number): number ES6

    返回x的双曲余弦值。

  • Math.hypot(...values: Array<number>): number ES6

    返回 values 的平方和的平方根(毕达哥拉斯定理):

    > Math.hypot(3, 4)
    5
    
    
  • Math.sin(x: number): number ES1

    返回 x 的正弦。

    > Math.sin(0)
    0
    > Math.sin(Math.PI / 2)
    1
    
    
  • Math.sinh(x: number): number ES6

    返回 x 的双曲正弦。

  • Math.tan(x: number): number ES1

    返回 x 的正切。

    > Math.tan(0)
    0
    > Math.tan(1)
    1.5574077246549023
    
    
  • Math.tanh(x: number): number; ES6

    返回 x 的双曲正切。

19.5 其他各种函数

  • Math.abs(x: number): number ES1

    返回 x 的绝对值。

    > Math.abs(3)
    3
    > Math.abs(-3)
    3
    > Math.abs(0)
    0
    
    
  • Math.clz32(x: number): number ES6

    计算在 32 位整数 x 中的前导零位。用于数字信号处理算法。

    > Math.clz32(0b01000000000000000000000000000000)
    1
    > Math.clz32(0b00100000000000000000000000000000)
    2
    > Math.clz32(2)
    30
    > Math.clz32(1)
    31
    
    
  • Math.max(...values: Array<number>): number ES1

    values 转换为数字并返回最大值。

    > Math.max(3, -5, 24)
    24
    
    
  • Math.min(...values: Array<number>): number ES1

    values 转换为数字并返回最小值。

    > Math.min(3, -5, 24)
    -5
    
    
  • Math.random(): number ES1

    返回一个伪随机数 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): number ES6

    返回一个数字的符号:

    > Math.sign(-8)
    -1
    > Math.sign(0)
    0
    > Math.sign(3)
    1
    
    

19.6 源

posted @ 2025-12-12 18:01  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报