JavaScript|第13章:原始类型的方法

JavaScript|第13章:原始类型的方法

原始类型的方法

  • JavaScript|第13章:原始类型的方法
    • 引言
    • [13.1 原始类型为什么能有方法?](#13.1 原始类型为什么能有方法?)
      • [13.1.1 原始类型 vs 对象类型](#13.1.1 原始类型 vs 对象类型)
      • [13.1.2 装箱机制](#13.1.2 装箱机制)
      • [13.1.3 装箱的临时性](#13.1.3 装箱的临时性)
      • [13.1.4 null 和 undefined 没有方法](#13.1.4 null 和 undefined 没有方法)
      • [13.1.5 原始类型与包装对象的区别](#13.1.5 原始类型与包装对象的区别)
    • [13.2 数字类型的方法](#13.2 数字类型的方法)
      • [13.2.1 数字字面量调用方法的坑](#13.2.1 数字字面量调用方法的坑)
      • [13.2.2 toFixed --- 格式化小数](#13.2.2 toFixed — 格式化小数)
      • [13.2.3 toString --- 进制转换](#13.2.3 toString — 进制转换)
      • [13.2.4 toPrecision --- 有效数字](#13.2.4 toPrecision — 有效数字)
      • [13.2.5 Number 构造函数的静态方法](#13.2.5 Number 构造函数的静态方法)
      • [13.2.6 全局方法 vs Number 静态方法](#13.2.6 全局方法 vs Number 静态方法)
      • [13.2.7 parseInt 与 parseFloat](#13.2.7 parseInt 与 parseFloat)
      • [13.2.8 Number() vs parseInt() vs parseFloat()](#13.2.8 Number() vs parseInt() vs parseFloat())
      • [13.2.9 数字的特殊值](#13.2.9 数字的特殊值)
    • [13.3 精度问题与 BigInt](#13.3 精度问题与 BigInt)
      • [13.3.1 IEEE 754 浮点数](#13.3.1 IEEE 754 浮点数)
      • [13.3.2 经典精度问题:0.1 + 0.2 !== 0.3](#13.3.2 经典精度问题:0.1 + 0.2 !== 0.3)
      • [13.3.3 Number.EPSILON](#13.3.3 Number.EPSILON)
      • [13.3.4 数字的安全范围](#13.3.4 数字的安全范围)
      • [13.3.5 BigInt --- 大整数类型](#13.3.5 BigInt — 大整数类型)
      • [13.3.6 Number 与 BigInt 的互转](#13.3.6 Number 与 BigInt 的互转)
    • [13.4 字符串与布尔的装箱方法](#13.4 字符串与布尔的装箱方法)
      • [13.4.1 字符串的装箱方法](#13.4.1 字符串的装箱方法)
      • [13.4.2 布尔的装箱方法](#13.4.2 布尔的装箱方法)
      • [13.4.3 Symbol 的装箱](#13.4.3 Symbol 的装箱)
    • [13.5 实战](#13.5 实战)
      • [13.5.1 实战1:金额格式化](#13.5.1 实战1:金额格式化)
      • [13.5.2 实战2:精度安全计算](#13.5.2 实战2:精度安全计算)
      • [13.5.3 实战3:进制转换工具](#13.5.3 实战3:进制转换工具)
      • [13.5.4 实战4:大数计算器](#13.5.4 实战4:大数计算器)
      • [13.5.5 实战5:数字类型检测与安全转换](#13.5.5 实战5:数字类型检测与安全转换)
    • [13.6 原始类型方法速查表](#13.6 原始类型方法速查表)
      • [13.6.1 数字方法速查](#13.6.1 数字方法速查)
      • [13.6.2 Number 静态常量速查](#13.6.2 Number 静态常量速查)
      • [13.6.3 转换方法对比](#13.6.3 转换方法对比)
    • 本章小结

上篇回顾第12章:属性配置与 getter/setter

下篇预告第14章:字符串


引言

在前面的章节中,我们学习了对象、属性描述符、getter/setter。你可能会产生一个疑问:

原始类型(numberstringboolean)明明不是对象,为什么能调用 .toFixed().toUpperCase() 这样的方法?

javascript 复制代码
const num = 3.14159;
console.log(num.toFixed(2));  // "3.14" ← number 不是对象,却能调用方法!

const str = 'hello';
console.log(str.toUpperCase()); // "HELLO" ← string 不是对象,却能调用方法!

const bool = true;
console.log(bool.toString());   // "true" ← boolean 不是对象,却能调用方法!

这背后的机制称为装箱(Boxing)------JavaScript 引擎在原始类型调用方法时,临时将其"包装"成对应的对象类型,方法执行完毕后立即"拆箱"销毁。
#mermaid-svg-Xrprr8GVbRIVDykK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Xrprr8GVbRIVDykK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Xrprr8GVbRIVDykK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Xrprr8GVbRIVDykK .error-icon{fill:#552222;}#mermaid-svg-Xrprr8GVbRIVDykK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Xrprr8GVbRIVDykK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Xrprr8GVbRIVDykK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Xrprr8GVbRIVDykK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Xrprr8GVbRIVDykK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Xrprr8GVbRIVDykK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Xrprr8GVbRIVDykK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Xrprr8GVbRIVDykK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Xrprr8GVbRIVDykK .marker.cross{stroke:#333333;}#mermaid-svg-Xrprr8GVbRIVDykK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Xrprr8GVbRIVDykK p{margin:0;}#mermaid-svg-Xrprr8GVbRIVDykK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Xrprr8GVbRIVDykK .cluster-label text{fill:#333;}#mermaid-svg-Xrprr8GVbRIVDykK .cluster-label span{color:#333;}#mermaid-svg-Xrprr8GVbRIVDykK .cluster-label span p{background-color:transparent;}#mermaid-svg-Xrprr8GVbRIVDykK .label text,#mermaid-svg-Xrprr8GVbRIVDykK span{fill:#333;color:#333;}#mermaid-svg-Xrprr8GVbRIVDykK .node rect,#mermaid-svg-Xrprr8GVbRIVDykK .node circle,#mermaid-svg-Xrprr8GVbRIVDykK .node ellipse,#mermaid-svg-Xrprr8GVbRIVDykK .node polygon,#mermaid-svg-Xrprr8GVbRIVDykK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Xrprr8GVbRIVDykK .rough-node .label text,#mermaid-svg-Xrprr8GVbRIVDykK .node .label text,#mermaid-svg-Xrprr8GVbRIVDykK .image-shape .label,#mermaid-svg-Xrprr8GVbRIVDykK .icon-shape .label{text-anchor:middle;}#mermaid-svg-Xrprr8GVbRIVDykK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Xrprr8GVbRIVDykK .rough-node .label,#mermaid-svg-Xrprr8GVbRIVDykK .node .label,#mermaid-svg-Xrprr8GVbRIVDykK .image-shape .label,#mermaid-svg-Xrprr8GVbRIVDykK .icon-shape .label{text-align:center;}#mermaid-svg-Xrprr8GVbRIVDykK .node.clickable{cursor:pointer;}#mermaid-svg-Xrprr8GVbRIVDykK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Xrprr8GVbRIVDykK .arrowheadPath{fill:#333333;}#mermaid-svg-Xrprr8GVbRIVDykK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Xrprr8GVbRIVDykK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Xrprr8GVbRIVDykK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xrprr8GVbRIVDykK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Xrprr8GVbRIVDykK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xrprr8GVbRIVDykK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Xrprr8GVbRIVDykK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Xrprr8GVbRIVDykK .cluster text{fill:#333;}#mermaid-svg-Xrprr8GVbRIVDykK .cluster span{color:#333;}#mermaid-svg-Xrprr8GVbRIVDykK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Xrprr8GVbRIVDykK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Xrprr8GVbRIVDykK rect.text{fill:none;stroke-width:0;}#mermaid-svg-Xrprr8GVbRIVDykK .icon-shape,#mermaid-svg-Xrprr8GVbRIVDykK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xrprr8GVbRIVDykK .icon-shape p,#mermaid-svg-Xrprr8GVbRIVDykK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Xrprr8GVbRIVDykK .icon-shape .label rect,#mermaid-svg-Xrprr8GVbRIVDykK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xrprr8GVbRIVDykK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Xrprr8GVbRIVDykK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Xrprr8GVbRIVDykK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始类型的方法
装箱与拆箱机制
数字类型方法
精度问题与 BigInt
实战应用
number → Number 对象
string → String 对象
boolean → Boolean 对象
toFixed / toString
parseInt / parseFloat
Number 静态方法
IEEE 754 浮点数
0.1 + 0.2 !== 0.3
BigInt 大整数
金额格式化
精度安全计算

本章目标

  • 理解原始类型调用方法的底层原理------装箱与拆箱
  • 掌握数字类型的常用方法:toFixedtoStringtoPrecision
  • 掌握 parseIntparseFloatNumber() 的区别与选择
  • 理解 IEEE 754 浮点数精度问题的根源
  • 掌握 BigInt 的使用场景与限制
  • 实战:金额格式化、精度安全计算

13.1 原始类型为什么能有方法?

13.1.1 原始类型 vs 对象类型

JavaScript 中有 7 种原始类型:numberstringbooleannullundefinedsymbolbigint。它们是轻量、不可变的值,存储在栈内存中。

特性 原始类型 对象类型
存储位置 栈内存(值直接存储) 堆内存(引用存储)
可变性 不可变 可变
能否有属性 ❌ 不能 ✅ 能
能否调用方法 ✅ 能(通过装箱) ✅ 能
比较 值比较 引用比较
javascript 复制代码
// 原始类型不可变
let str = 'hello';
str.foo = 'bar';     // 尝试添加属性
console.log(str.foo);  // undefined(添加失败)

// 对象类型可变
const obj = {};
obj.foo = 'bar';      // 添加属性
console.log(obj.foo);  // "bar"

关键问题 :既然原始类型不能有属性,那 str.toUpperCase() 为什么能正常工作?答案就是------装箱

13.1.2 装箱机制

当原始类型值被访问(调用方法或访问属性)时,JavaScript 引擎会自动执行以下步骤:

  1. 检测到属性访问 :引擎发现 str.toUpperCase()str 是原始类型
  2. 创建临时包装对象 :引擎创建一个 String 对象:new String(str)
  3. 在包装对象上执行操作 :调用包装对象的 toUpperCase 方法
  4. 销毁临时对象:操作完成后,包装对象立即被垃圾回收

#mermaid-svg-vWIWYYs5nEhdSaNJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vWIWYYs5nEhdSaNJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vWIWYYs5nEhdSaNJ .error-icon{fill:#552222;}#mermaid-svg-vWIWYYs5nEhdSaNJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vWIWYYs5nEhdSaNJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vWIWYYs5nEhdSaNJ .marker.cross{stroke:#333333;}#mermaid-svg-vWIWYYs5nEhdSaNJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vWIWYYs5nEhdSaNJ p{margin:0;}#mermaid-svg-vWIWYYs5nEhdSaNJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vWIWYYs5nEhdSaNJ .cluster-label text{fill:#333;}#mermaid-svg-vWIWYYs5nEhdSaNJ .cluster-label span{color:#333;}#mermaid-svg-vWIWYYs5nEhdSaNJ .cluster-label span p{background-color:transparent;}#mermaid-svg-vWIWYYs5nEhdSaNJ .label text,#mermaid-svg-vWIWYYs5nEhdSaNJ span{fill:#333;color:#333;}#mermaid-svg-vWIWYYs5nEhdSaNJ .node rect,#mermaid-svg-vWIWYYs5nEhdSaNJ .node circle,#mermaid-svg-vWIWYYs5nEhdSaNJ .node ellipse,#mermaid-svg-vWIWYYs5nEhdSaNJ .node polygon,#mermaid-svg-vWIWYYs5nEhdSaNJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vWIWYYs5nEhdSaNJ .rough-node .label text,#mermaid-svg-vWIWYYs5nEhdSaNJ .node .label text,#mermaid-svg-vWIWYYs5nEhdSaNJ .image-shape .label,#mermaid-svg-vWIWYYs5nEhdSaNJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-vWIWYYs5nEhdSaNJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vWIWYYs5nEhdSaNJ .rough-node .label,#mermaid-svg-vWIWYYs5nEhdSaNJ .node .label,#mermaid-svg-vWIWYYs5nEhdSaNJ .image-shape .label,#mermaid-svg-vWIWYYs5nEhdSaNJ .icon-shape .label{text-align:center;}#mermaid-svg-vWIWYYs5nEhdSaNJ .node.clickable{cursor:pointer;}#mermaid-svg-vWIWYYs5nEhdSaNJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vWIWYYs5nEhdSaNJ .arrowheadPath{fill:#333333;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vWIWYYs5nEhdSaNJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vWIWYYs5nEhdSaNJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vWIWYYs5nEhdSaNJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vWIWYYs5nEhdSaNJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vWIWYYs5nEhdSaNJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vWIWYYs5nEhdSaNJ .cluster text{fill:#333;}#mermaid-svg-vWIWYYs5nEhdSaNJ .cluster span{color:#333;}#mermaid-svg-vWIWYYs5nEhdSaNJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vWIWYYs5nEhdSaNJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vWIWYYs5nEhdSaNJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-vWIWYYs5nEhdSaNJ .icon-shape,#mermaid-svg-vWIWYYs5nEhdSaNJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vWIWYYs5nEhdSaNJ .icon-shape p,#mermaid-svg-vWIWYYs5nEhdSaNJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vWIWYYs5nEhdSaNJ .icon-shape .label rect,#mermaid-svg-vWIWYYs5nEhdSaNJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vWIWYYs5nEhdSaNJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vWIWYYs5nEhdSaNJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vWIWYYs5nEhdSaNJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} str.toUpperCase()
引擎检测到 str 是原始类型
创建临时对象 new String(str)
调用临时对象.toUpperCase()
返回 'HELLO'
销毁临时对象
str 仍然是原始类型 'hello'

我们可以手动模拟这个过程:

javascript 复制代码
const str = 'hello';

// 引擎在背后做了类似这样的事情:
const tempObj = new String(str);   // 装箱:创建 String 对象
const result = tempObj.toUpperCase(); // 在对象上调用方法
tempObj = null;                    // 拆箱:销毁临时对象(实际由 GC 回收)

console.log(result);   // "HELLO"
console.log(typeof str); // "string"(str 仍然是原始类型)

13.1.3 装箱的临时性

临时包装对象在表达式执行完毕后立即销毁,因此给它添加属性不会有任何效果:

javascript 复制代码
const str = 'hello';

// 每次访问都会创建新的临时对象
str.foo = 'bar';       // 装箱 → 添加属性 → 销毁
console.log(str.foo);   // 再次装箱(新对象,没有 foo)→ undefined

// 等价于:
(new String('hello')).foo = 'bar';   // 在临时对象上添加属性
(new String('hello')).foo;             // 创建了另一个新对象,没有 foo → undefined

#mermaid-svg-5KHjiHixkOZjLhey{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5KHjiHixkOZjLhey .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5KHjiHixkOZjLhey .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5KHjiHixkOZjLhey .error-icon{fill:#552222;}#mermaid-svg-5KHjiHixkOZjLhey .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5KHjiHixkOZjLhey .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5KHjiHixkOZjLhey .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5KHjiHixkOZjLhey .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5KHjiHixkOZjLhey .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5KHjiHixkOZjLhey .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5KHjiHixkOZjLhey .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5KHjiHixkOZjLhey .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5KHjiHixkOZjLhey .marker.cross{stroke:#333333;}#mermaid-svg-5KHjiHixkOZjLhey svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5KHjiHixkOZjLhey p{margin:0;}#mermaid-svg-5KHjiHixkOZjLhey .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5KHjiHixkOZjLhey .cluster-label text{fill:#333;}#mermaid-svg-5KHjiHixkOZjLhey .cluster-label span{color:#333;}#mermaid-svg-5KHjiHixkOZjLhey .cluster-label span p{background-color:transparent;}#mermaid-svg-5KHjiHixkOZjLhey .label text,#mermaid-svg-5KHjiHixkOZjLhey span{fill:#333;color:#333;}#mermaid-svg-5KHjiHixkOZjLhey .node rect,#mermaid-svg-5KHjiHixkOZjLhey .node circle,#mermaid-svg-5KHjiHixkOZjLhey .node ellipse,#mermaid-svg-5KHjiHixkOZjLhey .node polygon,#mermaid-svg-5KHjiHixkOZjLhey .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5KHjiHixkOZjLhey .rough-node .label text,#mermaid-svg-5KHjiHixkOZjLhey .node .label text,#mermaid-svg-5KHjiHixkOZjLhey .image-shape .label,#mermaid-svg-5KHjiHixkOZjLhey .icon-shape .label{text-anchor:middle;}#mermaid-svg-5KHjiHixkOZjLhey .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5KHjiHixkOZjLhey .rough-node .label,#mermaid-svg-5KHjiHixkOZjLhey .node .label,#mermaid-svg-5KHjiHixkOZjLhey .image-shape .label,#mermaid-svg-5KHjiHixkOZjLhey .icon-shape .label{text-align:center;}#mermaid-svg-5KHjiHixkOZjLhey .node.clickable{cursor:pointer;}#mermaid-svg-5KHjiHixkOZjLhey .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5KHjiHixkOZjLhey .arrowheadPath{fill:#333333;}#mermaid-svg-5KHjiHixkOZjLhey .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5KHjiHixkOZjLhey .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5KHjiHixkOZjLhey .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5KHjiHixkOZjLhey .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5KHjiHixkOZjLhey .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5KHjiHixkOZjLhey .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5KHjiHixkOZjLhey .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5KHjiHixkOZjLhey .cluster text{fill:#333;}#mermaid-svg-5KHjiHixkOZjLhey .cluster span{color:#333;}#mermaid-svg-5KHjiHixkOZjLhey div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5KHjiHixkOZjLhey .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5KHjiHixkOZjLhey rect.text{fill:none;stroke-width:0;}#mermaid-svg-5KHjiHixkOZjLhey .icon-shape,#mermaid-svg-5KHjiHixkOZjLhey .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5KHjiHixkOZjLhey .icon-shape p,#mermaid-svg-5KHjiHixkOZjLhey .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5KHjiHixkOZjLhey .icon-shape .label rect,#mermaid-svg-5KHjiHixkOZjLhey .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5KHjiHixkOZjLhey .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5KHjiHixkOZjLhey .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5KHjiHixkOZjLhey :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} str.foo = 'bar'
装箱:new String('hello')
设置 .foo = 'bar'
销毁临时对象
str.foo 读取
再次装箱:new String('hello')
新对象没有 foo 属性
返回 undefined

核心要点 :装箱是临时的、隐式的。每次属性访问都会创建一个新的包装对象,访问结束后立即销毁。

13.1.4 null 和 undefined 没有方法

并非所有原始类型都有对应的包装对象。nullundefined 是两个特殊的原始类型,它们没有包装对象,因此调用方法会直接报错:

javascript 复制代码
// null 和 undefined 没有方法
// null.toString();      // TypeError: Cannot read properties of null
// undefined.toString();  // TypeError: Cannot read properties of undefined

// 访问属性也会报错
// null.foo;              // TypeError
// undefined.foo;         // TypeError
原始类型 包装对象 能否调用方法
number Number
string String
boolean Boolean
symbol Symbol
bigint BigInt
null
undefined

最佳实践 :在访问可能为 nullundefined 的值的方法时,使用可选链 ?. 避免报错(回顾第 11 章的内容)。

13.1.5 原始类型与包装对象的区别

虽然 new Number(42)42 都能调用数字方法,但它们有本质区别:

javascript 复制代码
const primitive = 42;        // 原始类型
const wrapped = new Number(42); // 包装对象

console.log(typeof primitive); // "number"
console.log(typeof wrapped);   // "object"

console.log(primitive === 42);   // true
console.log(wrapped === 42);     // false(对象不等于原始值)
console.log(wrapped == 42);      // true(== 会触发拆箱转换)

// 包装对象可以添加属性(但不建议这样做)
wrapped.custom = 'hello';
console.log(wrapped.custom); // "hello"
特性 原始类型 42 包装对象 new Number(42)
typeof "number" "object"
=== 42 true false
可添加属性
推荐使用

强烈建议 :永远不要手动使用 new Number()new String()new Boolean() 创建包装对象。它们只会带来混乱和 Bug。


13.2 数字类型的方法

13.2.1 数字字面量调用方法的坑

数字字面量直接调用方法时,小数点会被误解为小数点而非成员访问符:

javascript 复制代码
// ❌ 错误:引擎将 5 .toFixed 解析为 5.toFixed → 语法错误
// 5.toFixed(2); // SyntaxError

// ✅ 正确写法1:用两个点(第一个是小数点,第二个是成员访问)
console.log(5..toFixed(2));   // "5.00"

// ✅ 正确写法2:用括号包裹数字
console.log((5).toFixed(2));  // "5.00"

// ✅ 正确写法3:变量调用
const num = 5;
console.log(num.toFixed(2)); // "5.00"

#mermaid-svg-8cr0rJZ4mQkGm6No{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8cr0rJZ4mQkGm6No .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8cr0rJZ4mQkGm6No .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8cr0rJZ4mQkGm6No .error-icon{fill:#552222;}#mermaid-svg-8cr0rJZ4mQkGm6No .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8cr0rJZ4mQkGm6No .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8cr0rJZ4mQkGm6No .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8cr0rJZ4mQkGm6No .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8cr0rJZ4mQkGm6No .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8cr0rJZ4mQkGm6No .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8cr0rJZ4mQkGm6No .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8cr0rJZ4mQkGm6No .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8cr0rJZ4mQkGm6No .marker.cross{stroke:#333333;}#mermaid-svg-8cr0rJZ4mQkGm6No svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8cr0rJZ4mQkGm6No p{margin:0;}#mermaid-svg-8cr0rJZ4mQkGm6No .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8cr0rJZ4mQkGm6No .cluster-label text{fill:#333;}#mermaid-svg-8cr0rJZ4mQkGm6No .cluster-label span{color:#333;}#mermaid-svg-8cr0rJZ4mQkGm6No .cluster-label span p{background-color:transparent;}#mermaid-svg-8cr0rJZ4mQkGm6No .label text,#mermaid-svg-8cr0rJZ4mQkGm6No span{fill:#333;color:#333;}#mermaid-svg-8cr0rJZ4mQkGm6No .node rect,#mermaid-svg-8cr0rJZ4mQkGm6No .node circle,#mermaid-svg-8cr0rJZ4mQkGm6No .node ellipse,#mermaid-svg-8cr0rJZ4mQkGm6No .node polygon,#mermaid-svg-8cr0rJZ4mQkGm6No .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8cr0rJZ4mQkGm6No .rough-node .label text,#mermaid-svg-8cr0rJZ4mQkGm6No .node .label text,#mermaid-svg-8cr0rJZ4mQkGm6No .image-shape .label,#mermaid-svg-8cr0rJZ4mQkGm6No .icon-shape .label{text-anchor:middle;}#mermaid-svg-8cr0rJZ4mQkGm6No .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8cr0rJZ4mQkGm6No .rough-node .label,#mermaid-svg-8cr0rJZ4mQkGm6No .node .label,#mermaid-svg-8cr0rJZ4mQkGm6No .image-shape .label,#mermaid-svg-8cr0rJZ4mQkGm6No .icon-shape .label{text-align:center;}#mermaid-svg-8cr0rJZ4mQkGm6No .node.clickable{cursor:pointer;}#mermaid-svg-8cr0rJZ4mQkGm6No .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8cr0rJZ4mQkGm6No .arrowheadPath{fill:#333333;}#mermaid-svg-8cr0rJZ4mQkGm6No .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8cr0rJZ4mQkGm6No .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8cr0rJZ4mQkGm6No .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8cr0rJZ4mQkGm6No .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8cr0rJZ4mQkGm6No .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8cr0rJZ4mQkGm6No .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8cr0rJZ4mQkGm6No .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8cr0rJZ4mQkGm6No .cluster text{fill:#333;}#mermaid-svg-8cr0rJZ4mQkGm6No .cluster span{color:#333;}#mermaid-svg-8cr0rJZ4mQkGm6No div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8cr0rJZ4mQkGm6No .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8cr0rJZ4mQkGm6No rect.text{fill:none;stroke-width:0;}#mermaid-svg-8cr0rJZ4mQkGm6No .icon-shape,#mermaid-svg-8cr0rJZ4mQkGm6No .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8cr0rJZ4mQkGm6No .icon-shape p,#mermaid-svg-8cr0rJZ4mQkGm6No .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8cr0rJZ4mQkGm6No .icon-shape .label rect,#mermaid-svg-8cr0rJZ4mQkGm6No .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8cr0rJZ4mQkGm6No .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8cr0rJZ4mQkGm6No .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8cr0rJZ4mQkGm6No :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 5.toFixed(2)
引擎解析
5. 被解析为浮点数 5.0
toFixed 变成标识符
语法错误:5.0 toFixed
5..toFixed(2)
第一个 . 是小数点

第二个 . 是成员访问
正确:(5.0).toFixed(2)

最佳实践:使用变量或括号调用方法,避免直接在字面量上调用。

13.2.2 toFixed --- 格式化小数

toFixed(digits) 将数字转换为字符串,保留指定小数位数,四舍五入

javascript 复制代码
const num = 3.14159;

console.log(num.toFixed(0));   // "3"(0 位小数)
console.log(num.toFixed(1));   // "3.1"(1 位小数)
console.log(num.toFixed(2));   // "3.14"(2 位小数)
console.log(num.toFixed(4));   // "3.1416"(4 位小数,四舍五入)

// 注意:返回的是字符串,不是数字
console.log(typeof num.toFixed(2)); // "string"

// 转回数字可以用 Number() 或 +
console.log(Number(num.toFixed(2))); // 3.14
console.log(+num.toFixed(2));        // 3.14

toFixed 的四舍五入陷阱

toFixed 使用的是"银行家舍入法"的变体,并非标准的四舍五入。由于浮点数精度问题,某些值的行为可能出乎意料:

javascript 复制代码
// 正常的四舍五入
console.log((1.005).toFixed(2)); // "1.00" ← 期望 "1.01",但得到 "1.00"
console.log((1.015).toFixed(2)); // "1.02" ← 正确
console.log((1.025).toFixed(2)); // "1.02" ← 期望 "1.03",但得到 "1.02"
console.log((1.035).toFixed(2)); // "1.04" ← 正确
console.log((0.5).toFixed(0));   // "0" ← 期望 "1",但得到 "0"(某些引擎)
console.log((1.5).toFixed(0));   // "2" ← 正确

原因1.005 在 IEEE 754 中的实际存储值约为 1.0049999999999999...toFixed 基于这个实际值进行舍入,因此结果为 "1.00"

解决 toFixed 精度问题的方案

javascript 复制代码
/**
 * 精确的四舍五入
 * @param {number} num - 要舍入的数字
 * @param {number} digits - 保留小数位数
 * @returns {string} 格式化后的字符串
 */
function toFixedPrecise(num, digits) {
  // 使用 Number.EPSILON 修正精度问题
  const factor = Math.pow(10, digits);
  const corrected = Math.round((num + Number.EPSILON) * factor) / factor;
  return corrected.toFixed(digits);
}

console.log(toFixedPrecise(1.005, 2)); // "1.01" ✅
console.log(toFixedPrecise(1.025, 2)); // "1.03" ✅
console.log(toFixedPrecise(0.5, 0));   // "1"    ✅

13.2.3 toString --- 进制转换

toString(radix) 将数字转换为指定进制的字符串表示。

javascript 复制代码
const num = 255;

// 默认十进制
console.log(num.toString());     // "255"

// 转换为其他进制
console.log(num.toString(2));    // "11111111"(二进制)
console.log(num.toString(8));    // "377"(八进制)
console.log(num.toString(16));   // "ff"(十六进制)
console.log(num.toString(36));   // "73"(三十六进制,最高支持)

// 常用场景:生成随机十六进制颜色
function randomColor() {
  const r = Math.floor(Math.random() * 256).toString(16).padStart(2, '0');
  const g = Math.floor(Math.random() * 256).toString(16).padStart(2, '0');
  const b = Math.floor(Math.random() * 256).toString(16).padStart(2, '0');
  return `#${r}${g}${b}`;
}

console.log(randomColor()); // 例如 "#a3f5b1"

反向操作:从其他进制转回十进制

javascript 复制代码
// parseInt(str, radix) 将字符串按指定进制解析为十进制数字
console.log(parseInt('11111111', 2)); // 255
console.log(parseInt('ff', 16));       // 255
console.log(parseInt('377', 8));       // 255

// ES2015+ 可以用 0b、0o、0x 前缀直接写进制字面量
console.log(0b11111111); // 255(二进制)
console.log(0o377);      // 255(八进制)
console.log(0xff);       // 255(十六进制)
方法 方向 示例
num.toString(radix) 十进制 → 其他进制 (255).toString(16)"ff"
parseInt(str, radix) 其他进制 → 十进制 parseInt('ff', 16)255
0b / 0o / 0x 前缀 进制字面量 → 十进制 0xff255

13.2.4 toPrecision --- 有效数字

toPrecision(precision) 指定有效数字的位数(包括整数部分和小数部分)。

javascript 复制代码
const num = 123.456;

console.log(num.toPrecision(2)); // "1.2e+2"(科学计数法)
console.log(num.toPrecision(3)); // "123"
console.log(num.toPrecision(4)); // "123.5"
console.log(num.toPrecision(6)); // "123.456"
console.log(num.toPrecision(8)); // "123.45600"(补零)

toFixed vs toPrecision 的区别

方法 控制内容 示例(num = 123.456)
toFixed(2) 小数点后位数 "123.46"
toPrecision(2) 总有效数字位数 "1.2e+2"

13.2.5 Number 构造函数的静态方法

Number 对象提供了一系列静态方法,用于判断数字的类别:

javascript 复制代码
// Number.isInteger:判断是否为整数
console.log(Number.isInteger(42));     // true
console.log(Number.isInteger(42.0));   // true
console.log(Number.isInteger(42.5));   // false
console.log(Number.isInteger('42'));    // false(不转换类型)
console.log(Number.isInteger(NaN));    // false

// Number.isFinite:判断是否为有限数
console.log(Number.isFinite(42));      // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite(NaN));     // false
console.log(Number.isFinite('42'));    // false(不转换类型)

// Number.isNaN:判断是否为 NaN
console.log(Number.isNaN(NaN));        // true
console.log(Number.isNaN(42));         // false
console.log(Number.isNaN('NaN'));     // false(不转换类型)
console.log(Number.isNaN(undefined));  // false

13.2.6 全局方法 vs Number 静态方法

全局的 isFinite()isNaN()Number.isFinite()Number.isNaN() 有重要区别:

javascript 复制代码
// 全局 isNaN:会先尝试转换为数字,再判断
console.log(isNaN('hello'));     // true('hello' 转为 NaN)
console.log(isNaN(undefined));   // true(undefined 转为 NaN)
console.log(isNaN(''));          // false('' 转为 0)
console.log(isNaN('123'));       // false('123' 转为 123)

// Number.isNaN:不转换类型,严格判断
console.log(Number.isNaN('hello'));    // false(类型不是 number)
console.log(Number.isNaN(undefined));  // false
console.log(Number.isNaN(NaN));        // true(只有真正的 NaN 才返回 true)

// 全局 isFinite:会先转换
console.log(isFinite('42'));      // true('42' 转为 42,是有限数)
console.log(isFinite(null));      // true(null 转为 0,是有限数)

// Number.isFinite:不转换
console.log(Number.isFinite('42'));  // false(类型不是 number)
console.log(Number.isFinite(42));    // true

#mermaid-svg-KrzmALn7gUNjj6PH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KrzmALn7gUNjj6PH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KrzmALn7gUNjj6PH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KrzmALn7gUNjj6PH .error-icon{fill:#552222;}#mermaid-svg-KrzmALn7gUNjj6PH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KrzmALn7gUNjj6PH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KrzmALn7gUNjj6PH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KrzmALn7gUNjj6PH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KrzmALn7gUNjj6PH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KrzmALn7gUNjj6PH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KrzmALn7gUNjj6PH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KrzmALn7gUNjj6PH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KrzmALn7gUNjj6PH .marker.cross{stroke:#333333;}#mermaid-svg-KrzmALn7gUNjj6PH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KrzmALn7gUNjj6PH p{margin:0;}#mermaid-svg-KrzmALn7gUNjj6PH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KrzmALn7gUNjj6PH .cluster-label text{fill:#333;}#mermaid-svg-KrzmALn7gUNjj6PH .cluster-label span{color:#333;}#mermaid-svg-KrzmALn7gUNjj6PH .cluster-label span p{background-color:transparent;}#mermaid-svg-KrzmALn7gUNjj6PH .label text,#mermaid-svg-KrzmALn7gUNjj6PH span{fill:#333;color:#333;}#mermaid-svg-KrzmALn7gUNjj6PH .node rect,#mermaid-svg-KrzmALn7gUNjj6PH .node circle,#mermaid-svg-KrzmALn7gUNjj6PH .node ellipse,#mermaid-svg-KrzmALn7gUNjj6PH .node polygon,#mermaid-svg-KrzmALn7gUNjj6PH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KrzmALn7gUNjj6PH .rough-node .label text,#mermaid-svg-KrzmALn7gUNjj6PH .node .label text,#mermaid-svg-KrzmALn7gUNjj6PH .image-shape .label,#mermaid-svg-KrzmALn7gUNjj6PH .icon-shape .label{text-anchor:middle;}#mermaid-svg-KrzmALn7gUNjj6PH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KrzmALn7gUNjj6PH .rough-node .label,#mermaid-svg-KrzmALn7gUNjj6PH .node .label,#mermaid-svg-KrzmALn7gUNjj6PH .image-shape .label,#mermaid-svg-KrzmALn7gUNjj6PH .icon-shape .label{text-align:center;}#mermaid-svg-KrzmALn7gUNjj6PH .node.clickable{cursor:pointer;}#mermaid-svg-KrzmALn7gUNjj6PH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KrzmALn7gUNjj6PH .arrowheadPath{fill:#333333;}#mermaid-svg-KrzmALn7gUNjj6PH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KrzmALn7gUNjj6PH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KrzmALn7gUNjj6PH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KrzmALn7gUNjj6PH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KrzmALn7gUNjj6PH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KrzmALn7gUNjj6PH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KrzmALn7gUNjj6PH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KrzmALn7gUNjj6PH .cluster text{fill:#333;}#mermaid-svg-KrzmALn7gUNjj6PH .cluster span{color:#333;}#mermaid-svg-KrzmALn7gUNjj6PH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KrzmALn7gUNjj6PH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KrzmALn7gUNjj6PH rect.text{fill:none;stroke-width:0;}#mermaid-svg-KrzmALn7gUNjj6PH .icon-shape,#mermaid-svg-KrzmALn7gUNjj6PH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KrzmALn7gUNjj6PH .icon-shape p,#mermaid-svg-KrzmALn7gUNjj6PH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KrzmALn7gUNjj6PH .icon-shape .label rect,#mermaid-svg-KrzmALn7gUNjj6PH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KrzmALn7gUNjj6PH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KrzmALn7gUNjj6PH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KrzmALn7gUNjj6PH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



isNaN(value)
先转换为数字
结果 === NaN?
true
false
Number.isNaN(value)
typeof === 'number' 且值 === NaN?
true
false

方法 是否转换类型 推荐使用
isNaN() ✅ 会先转换 ❌ 容易产生误判
Number.isNaN() ❌ 不转换 ✅ 严格准确
isFinite() ✅ 会先转换
Number.isFinite() ❌ 不转换

最佳实践 :始终使用 Number.isNaN()Number.isFinite(),避免全局方法的类型转换陷阱。

13.2.7 parseInt 与 parseFloat

parseIntparseFloat 用于将字符串解析为数字,它们会从左到右逐字符解析,遇到无效字符时停止。

javascript 复制代码
// parseInt:解析整数
console.log(parseInt('42'));       // 42
console.log(parseInt('42.5'));     // 42(小数部分被忽略)
console.log(parseInt('42px'));     // 42(遇到非数字字符停止)
console.log(parseInt('  42  '));  // 42(忽略前后空格)
console.log(parseInt('abc'));      // NaN(第一个字符就不是数字)
console.log(parseInt(''));         // NaN(空字符串)
console.log(parseInt('0xff'));    // 255(十六进制前缀)
console.log(parseInt('0o17'));     // 0(不识别八进制前缀,截断为 0)
console.log(parseInt('0b101'));    // 0(不识别二进制前缀,截断为 0)

// parseFloat:解析浮点数
console.log(parseFloat('3.14'));    // 3.14
console.log(parseFloat('3.14abc')); // 3.14
console.log(parseFloat('3.'));      // 3
console.log(parseFloat('.5'));      // 0.5
console.log(parseFloat('abc'));     // NaN

parseInt 的进制参数

javascript 复制代码
// parseInt(str, radix):radix 指定字符串的进制
console.log(parseInt('10', 2));    // 2(二进制的 10 = 十进制的 2)
console.log(parseInt('10', 8));    // 8(八进制的 10 = 十进制的 8)
console.log(parseInt('10', 10));   // 10(十进制)
console.log(parseInt('10', 16));  // 16(十六进制的 10 = 十进制的 16)
console.log(parseInt('ff', 16));   // 255

// 不传 radix 的陷阱(旧版引擎中 '0' 开头会被解析为八进制)
console.log(parseInt('010'));      // 10(现代引擎默认十进制)
console.log(parseInt('010', 10));  // 10(显式指定十进制)

最佳实践 :使用 parseInt始终传入第二个参数 radix,避免不同引擎的行为差异。

13.2.8 Number() vs parseInt() vs parseFloat()

三者都能将字符串转为数字,但行为不同:

javascript 复制代码
// Number():严格解析,整个字符串必须是合法数字
console.log(Number('42'));      // 42
console.log(Number('42.5'));    // 42.5
console.log(Number('42px'));    // NaN(包含非数字字符)
console.log(Number(''));        // 0(空字符串转为 0)
console.log(Number('  '));      // 0(纯空格转为 0)
console.log(Number(null));     // 0
console.log(Number(undefined)); // NaN
console.log(Number(true));     // 1
console.log(Number(false));    // 0

// parseInt():宽松解析,遇到非数字字符停止
console.log(parseInt('42'));     // 42
console.log(parseInt('42.5'));   // 42
console.log(parseInt('42px'));   // 42
console.log(parseInt(''));        // NaN
console.log(parseInt(null));     // NaN
console.log(parseInt(true));    // NaN

// parseFloat():宽松解析,保留小数
console.log(parseFloat('42'));     // 42
console.log(parseFloat('42.5'));   // 42.5
console.log(parseFloat('42.5px')); // 42.5
console.log(parseFloat(''));       // NaN
方法 '42px' '' null true '3.14'
Number() NaN 0 0 1 3.14
parseInt() 42 NaN NaN NaN 3
parseFloat() 42 NaN NaN NaN 3.14

选择建议

  • 需要严格验证整个字符串是否为合法数字 → Number()
  • 从混合字符串中提取整数 → parseInt(str, 10)
  • 从混合字符串中提取浮点数 → parseFloat(str)

13.2.9 数字的特殊值

javascript 复制代码
// NaN:Not a Number(非数字)
console.log(typeof NaN);          // "number"(NaN 的类型竟然是 number!)
console.log(NaN === NaN);         // false(NaN 不等于自身)
console.log(NaN !== NaN);         // true
console.log(Number.isNaN(NaN));   // true(正确的判断方式)

// Infinity:无穷大
console.log(1 / 0);               // Infinity
console.log(-1 / 0);              // -Infinity
console.log(typeof Infinity);     // "number"
console.log(Infinity > 9999999);  // true

// -0:负零
console.log(-0);                  // -0
console.log(0 === -0);            // true(0 和 -0 相等)
console.log(Object.is(0, -0));    // false(Object.is 能区分)
console.log(1 / -0);              // -Infinity(符号不同)

13.3 精度问题与 BigInt

13.3.1 IEEE 754 浮点数

JavaScript 中的所有数字(bigint 除外)都使用 IEEE 754 双精度浮点数(64-bit)格式存储。一个 64 位浮点数由三部分组成:
#mermaid-svg-1rDy0xe8tMCkKHrz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1rDy0xe8tMCkKHrz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1rDy0xe8tMCkKHrz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1rDy0xe8tMCkKHrz .error-icon{fill:#552222;}#mermaid-svg-1rDy0xe8tMCkKHrz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1rDy0xe8tMCkKHrz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1rDy0xe8tMCkKHrz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1rDy0xe8tMCkKHrz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1rDy0xe8tMCkKHrz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1rDy0xe8tMCkKHrz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1rDy0xe8tMCkKHrz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1rDy0xe8tMCkKHrz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1rDy0xe8tMCkKHrz .marker.cross{stroke:#333333;}#mermaid-svg-1rDy0xe8tMCkKHrz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1rDy0xe8tMCkKHrz p{margin:0;}#mermaid-svg-1rDy0xe8tMCkKHrz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1rDy0xe8tMCkKHrz .cluster-label text{fill:#333;}#mermaid-svg-1rDy0xe8tMCkKHrz .cluster-label span{color:#333;}#mermaid-svg-1rDy0xe8tMCkKHrz .cluster-label span p{background-color:transparent;}#mermaid-svg-1rDy0xe8tMCkKHrz .label text,#mermaid-svg-1rDy0xe8tMCkKHrz span{fill:#333;color:#333;}#mermaid-svg-1rDy0xe8tMCkKHrz .node rect,#mermaid-svg-1rDy0xe8tMCkKHrz .node circle,#mermaid-svg-1rDy0xe8tMCkKHrz .node ellipse,#mermaid-svg-1rDy0xe8tMCkKHrz .node polygon,#mermaid-svg-1rDy0xe8tMCkKHrz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1rDy0xe8tMCkKHrz .rough-node .label text,#mermaid-svg-1rDy0xe8tMCkKHrz .node .label text,#mermaid-svg-1rDy0xe8tMCkKHrz .image-shape .label,#mermaid-svg-1rDy0xe8tMCkKHrz .icon-shape .label{text-anchor:middle;}#mermaid-svg-1rDy0xe8tMCkKHrz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1rDy0xe8tMCkKHrz .rough-node .label,#mermaid-svg-1rDy0xe8tMCkKHrz .node .label,#mermaid-svg-1rDy0xe8tMCkKHrz .image-shape .label,#mermaid-svg-1rDy0xe8tMCkKHrz .icon-shape .label{text-align:center;}#mermaid-svg-1rDy0xe8tMCkKHrz .node.clickable{cursor:pointer;}#mermaid-svg-1rDy0xe8tMCkKHrz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1rDy0xe8tMCkKHrz .arrowheadPath{fill:#333333;}#mermaid-svg-1rDy0xe8tMCkKHrz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1rDy0xe8tMCkKHrz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1rDy0xe8tMCkKHrz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1rDy0xe8tMCkKHrz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1rDy0xe8tMCkKHrz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1rDy0xe8tMCkKHrz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1rDy0xe8tMCkKHrz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1rDy0xe8tMCkKHrz .cluster text{fill:#333;}#mermaid-svg-1rDy0xe8tMCkKHrz .cluster span{color:#333;}#mermaid-svg-1rDy0xe8tMCkKHrz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1rDy0xe8tMCkKHrz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1rDy0xe8tMCkKHrz rect.text{fill:none;stroke-width:0;}#mermaid-svg-1rDy0xe8tMCkKHrz .icon-shape,#mermaid-svg-1rDy0xe8tMCkKHrz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1rDy0xe8tMCkKHrz .icon-shape p,#mermaid-svg-1rDy0xe8tMCkKHrz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1rDy0xe8tMCkKHrz .icon-shape .label rect,#mermaid-svg-1rDy0xe8tMCkKHrz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1rDy0xe8tMCkKHrz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1rDy0xe8tMCkKHrz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1rDy0xe8tMCkKHrz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 64 bit 浮点数
符号位 1 bit

Sign
指数位 11 bit

Exponent
尾数位 52 bit

Mantissa

部分 位数 说明
符号位(Sign) 1 bit 0 正数,1 负数
指数位(Exponent) 11 bit 决定数值的范围(大小)
尾数位(Mantissa) 52 bit 决定数值的精度(有效数字)

由于尾数只有 52 位,JavaScript 能精确表示的整数范围是有限的:

javascript 复制代码
// Number.MAX_SAFE_INTEGER:最大安全整数
console.log(Number.MAX_SAFE_INTEGER);  // 9007199254740991 (2^53 - 1)

// 超过这个范围,精度丢失
console.log(9007199254740992);     // 9007199254740992(恰好能表示)
console.log(9007199254740993);     // 9007199254740992 ← 精度丢失!与上面相同

// 判断是否在安全范围内
console.log(Number.isSafeInteger(9007199254740991)); // true
console.log(Number.isSafeInteger(9007199254740992)); // false

13.3.2 经典精度问题:0.1 + 0.2 !== 0.3

这是 JavaScript(以及所有使用 IEEE 754 的语言)中最著名的精度问题:

javascript 复制代码
console.log(0.1 + 0.2);             // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);    // false

// 更多精度问题
console.log(0.3 - 0.1);             // 0.19999999999999998
console.log(0.1 * 3);               // 0.30000000000000004
console.log(0.1 + 0.7);             // 0.7999999999999999
console.log(19.9 * 100);            // 1989.9999999999998

原因分析

十进制的 0.1 转换为二进制时是一个无限循环小数:

( 0.1 ) 10 = ( 0.0 0011 ‾ ) 2 = 0.0001100110011001100110011... (0.1){10} = (0.0\overline{0011}){2} = 0.0001100110011001100110011... (0.1)10=(0.00011)2=0.0001100110011001100110011...

由于尾数只有 52 位,这个无限循环小数被截断,产生了微小的误差。两个带有误差的数相加,误差被放大。
#mermaid-svg-ZCLWL46HIC4S62Vy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZCLWL46HIC4S62Vy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZCLWL46HIC4S62Vy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZCLWL46HIC4S62Vy .error-icon{fill:#552222;}#mermaid-svg-ZCLWL46HIC4S62Vy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZCLWL46HIC4S62Vy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZCLWL46HIC4S62Vy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZCLWL46HIC4S62Vy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZCLWL46HIC4S62Vy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZCLWL46HIC4S62Vy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZCLWL46HIC4S62Vy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZCLWL46HIC4S62Vy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZCLWL46HIC4S62Vy .marker.cross{stroke:#333333;}#mermaid-svg-ZCLWL46HIC4S62Vy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZCLWL46HIC4S62Vy p{margin:0;}#mermaid-svg-ZCLWL46HIC4S62Vy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZCLWL46HIC4S62Vy .cluster-label text{fill:#333;}#mermaid-svg-ZCLWL46HIC4S62Vy .cluster-label span{color:#333;}#mermaid-svg-ZCLWL46HIC4S62Vy .cluster-label span p{background-color:transparent;}#mermaid-svg-ZCLWL46HIC4S62Vy .label text,#mermaid-svg-ZCLWL46HIC4S62Vy span{fill:#333;color:#333;}#mermaid-svg-ZCLWL46HIC4S62Vy .node rect,#mermaid-svg-ZCLWL46HIC4S62Vy .node circle,#mermaid-svg-ZCLWL46HIC4S62Vy .node ellipse,#mermaid-svg-ZCLWL46HIC4S62Vy .node polygon,#mermaid-svg-ZCLWL46HIC4S62Vy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZCLWL46HIC4S62Vy .rough-node .label text,#mermaid-svg-ZCLWL46HIC4S62Vy .node .label text,#mermaid-svg-ZCLWL46HIC4S62Vy .image-shape .label,#mermaid-svg-ZCLWL46HIC4S62Vy .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZCLWL46HIC4S62Vy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZCLWL46HIC4S62Vy .rough-node .label,#mermaid-svg-ZCLWL46HIC4S62Vy .node .label,#mermaid-svg-ZCLWL46HIC4S62Vy .image-shape .label,#mermaid-svg-ZCLWL46HIC4S62Vy .icon-shape .label{text-align:center;}#mermaid-svg-ZCLWL46HIC4S62Vy .node.clickable{cursor:pointer;}#mermaid-svg-ZCLWL46HIC4S62Vy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZCLWL46HIC4S62Vy .arrowheadPath{fill:#333333;}#mermaid-svg-ZCLWL46HIC4S62Vy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZCLWL46HIC4S62Vy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZCLWL46HIC4S62Vy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZCLWL46HIC4S62Vy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZCLWL46HIC4S62Vy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZCLWL46HIC4S62Vy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZCLWL46HIC4S62Vy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZCLWL46HIC4S62Vy .cluster text{fill:#333;}#mermaid-svg-ZCLWL46HIC4S62Vy .cluster span{color:#333;}#mermaid-svg-ZCLWL46HIC4S62Vy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZCLWL46HIC4S62Vy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZCLWL46HIC4S62Vy rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZCLWL46HIC4S62Vy .icon-shape,#mermaid-svg-ZCLWL46HIC4S62Vy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZCLWL46HIC4S62Vy .icon-shape p,#mermaid-svg-ZCLWL46HIC4S62Vy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZCLWL46HIC4S62Vy .icon-shape .label rect,#mermaid-svg-ZCLWL46HIC4S62Vy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZCLWL46HIC4S62Vy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZCLWL46HIC4S62Vy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZCLWL46HIC4S62Vy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 十进制 0.1
转换为二进制

0.0001100110011...

无限循环
截断为 52 位尾数

精度丢失
存储值 ≈ 0.10000000000000000555
0.1 + 0.2 运算
0.30000000000000004

解决方案

javascript 复制代码
// 方案1:使用 Number.EPSILON 比较浮点数
const EPSILON = Number.EPSILON; // 2.220446049250313e-16

function isAlmostEqual(a, b) {
  return Math.abs(a - b) < EPSILON;
}

console.log(isAlmostEqual(0.1 + 0.2, 0.3)); // true

// 方案2:转换为整数运算(推荐用于金额计算)
function addPrecise(a, b) {
  // 找到两个数中最大的小数位数
  const aDecimals = (a.toString().split('.')[1] || '').length;
  const bDecimals = (b.toString().split('.')[1] || '').length;
  const maxDecimals = Math.max(aDecimals, bDecimals);
  const factor = Math.pow(10, maxDecimals);

  return (Math.round(a * factor) + Math.round(b * factor)) / factor;
}

console.log(addPrecise(0.1, 0.2));   // 0.3
console.log(addPrecise(0.3, 0.1));   // 0.4
console.log(addPrecise(19.9, 0.1));  // 20

13.3.3 Number.EPSILON

Number.EPSILON 表示 1 与大于 1 的最小浮点数之间的差值,是 JavaScript 中能表示的最小精度:

javascript 复制代码
console.log(Number.EPSILON); // 2.220446049250313e-16

// 用于浮点数相等比较
function floatEqual(a, b, epsilon = Number.EPSILON) {
  return Math.abs(a - b) < epsilon;
}

console.log(floatEqual(0.1 + 0.2, 0.3));  // true
console.log(floatEqual(0.3 - 0.1, 0.2));  // true

13.3.4 数字的安全范围

javascript 复制代码
// 最大安全整数
console.log(Number.MAX_SAFE_INTEGER);  // 9007199254740991

// 最小安全整数
console.log(Number.MIN_SAFE_INTEGER);  // -9007199254740991

// 最大正数
console.log(Number.MAX_VALUE);  // 1.7976931348623157e+308

// 最小正数(接近 0)
console.log(Number.MIN_VALUE);  // 5e-324

// 正无穷 / 负无穷
console.log(Number.POSITIVE_INFINITY);  // Infinity
console.log(Number.NEGATIVE_INFINITY);  // -Infinity
常量 说明
Number.MAX_SAFE_INTEGER 2 53 − 1 2^{53} - 1 253−1 最大安全整数
Number.MIN_SAFE_INTEGER − ( 2 53 − 1 ) -(2^{53} - 1) −(253−1) 最小安全整数
Number.MAX_VALUE ≈ 1.8 × 10 308 \approx 1.8 \times 10^{308} ≈1.8×10308 最大正数
Number.MIN_VALUE ≈ 5 × 10 − 324 \approx 5 \times 10^{-324} ≈5×10−324 最小正数(最接近 0)
Number.EPSILON ≈ 2.22 × 10 − 16 \approx 2.22 \times 10^{-16} ≈2.22×10−16 浮点数最小精度

13.3.5 BigInt --- 大整数类型

BigInt 是 ES2020 引入的新原始类型,用于表示任意精度 的整数,解决了 Number 无法精确表示大整数的问题。

javascript 复制代码
// 创建 BigInt:在数字末尾加 n
const big1 = 9007199254740993n;
console.log(big1); // 9007199254740993n(精确!)

// 使用 BigInt() 函数转换
const big2 = BigInt('9007199254740993');
console.log(big2); // 9007199254740993n

const big3 = BigInt(123);
console.log(big3); // 123n

// typeof 检测
console.log(typeof big1); // "bigint"

BigInt 的运算

javascript 复制代码
const a = 12345678901234567890n;
const b = 98765432109876543210n;

// 基本运算
console.log(a + b);  // 111111111011111111100n
console.log(a - b);  // -86419753208641975320n
console.log(a * b);  // 1219326311370217952237463801111263526900n
console.log(a / b);  // 0n(整数除法,截断小数)
console.log(a % b);  // 12345678901234567890n

// 比较
console.log(1n < 2n);  // true
console.log(2n > 1);   // true(可以与 number 比较)
console.log(1n === 1); // false(类型不同)
console.log(1n == 1);  // true(宽松相等,会转换)

BigInt 的限制

javascript 复制代码
// 1. 不能与 number 直接运算
// console.log(1n + 1); // TypeError: Cannot mix BigInt and other types

// 需要显式转换
console.log(1n + BigInt(1)); // 2n
console.log(Number(1n) + 1);  // 2

// 2. 不支持小数(结果是整数除法)
console.log(7n / 2n); // 3n(不是 3.5)

// 3. 不能用 Math 对象
// Math.sqrt(16n); // TypeError

// 4. 不能与 JSON 互转
// JSON.stringify(1n); // TypeError(默认不支持)
// 需要自定义 toJSON
BigInt.prototype.toJSON = function() { return Number(this); };
console.log(JSON.stringify({ count: 100n })); // '{"count":100}'
特性 Number BigInt
精度 最多 53 位整数 任意精度
小数 ✅ 支持 ❌ 不支持
与 number 混合运算 --- ❌ 需显式转换
Math 方法 ✅ 支持 ❌ 不支持
JSON.stringify ❌(需自定义)
typeof "number" "bigint"

使用建议 :只有在需要处理超过 Number.MAX_SAFE_INTEGER 的大整数时才使用 BigInt。日常的小数计算用整数运算方案或专门的库(如 decimal.js)更合适。

13.3.6 Number 与 BigInt 的互转

javascript 复制代码
// BigInt → Number(可能丢失精度)
const big = 9007199254740993n;
const num = Number(big);
console.log(num); // 9007199254740992(精度丢失!)

// 安全的转换:先检查范围
function bigIntToNumber(big) {
  if (big > Number.MAX_SAFE_INTEGER || big < Number.MIN_SAFE_INTEGER) {
    console.warn('BigInt 超出安全范围,转换可能导致精度丢失');
  }
  return Number(big);
}

// Number → BigInt
const n = 42;
const b = BigInt(n);
console.log(b); // 42n

// 注意:浮点数转 BigInt 会截断小数
console.log(BigInt(3.9)); // 3n(截断,不四舍五入)
// console.log(BigInt(3.14)); // RangeError(某些引擎中)

13.4 字符串与布尔的装箱方法

13.4.1 字符串的装箱方法

字符串原始类型在调用方法时会装箱为 String 对象。本章简要概述,详细方法将在第 14 章深入讲解。

javascript 复制代码
const str = 'Hello World';

// 这些方法都是通过装箱机制调用的
console.log(str.length);              // 11(访问属性也会装箱)
console.log(str.toUpperCase());        // "HELLO WORLD"
console.log(str.toLowerCase());        // "hello world"
console.log(str.charAt(0));            // "H"
console.log(str.indexOf('o'));         // 4
console.log(str.slice(0, 5));          // "Hello"
console.log(str.split(' '));          // ['Hello', 'World']
console.log(str.includes('World'));    // true
console.log(str.replace('World', 'JS')); // "Hello JS"

13.4.2 布尔的装箱方法

布尔原始类型的方法很少,主要是 toStringvalueOf

javascript 复制代码
const bool = true;

console.log(bool.toString());  // "true"
console.log(bool.valueOf());   // true

const bool2 = false;
console.log(bool2.toString()); // "false"

// 装箱后的 Boolean 对象有严重陷阱
const wrappedTrue = new Boolean(true);
const wrappedFalse = new Boolean(false);

console.log(typeof wrappedTrue);   // "object"
console.log(wrappedTrue === true);  // false
console.log(wrappedTrue == true);   // true

// ⚠️ 最危险的陷阱:Boolean 对象在条件判断中永远为 true
if (new Boolean(false)) {
  console.log('这会执行!'); // 因为对象总是 truthy
}

if (new Boolean(false).valueOf()) {
  console.log('这不会执行');
}

强烈警告 :永远不要使用 new Boolean() 创建布尔对象。if (new Boolean(false)) 会进入 if 分支,因为任何对象都是 truthy 值。

13.4.3 Symbol 的装箱

Symbol 是 ES6 引入的原始类型,它有一个特殊之处:不能使用 new Symbol() 创建包装对象

javascript 复制代码
const sym = Symbol('test');

// typeof
console.log(typeof sym); // "symbol"

// 装箱方法
console.log(sym.toString());   // "Symbol(test)"
console.log(sym.description);  // "test"

// Symbol 不能用 new
// const wrapped = new Symbol('test'); // TypeError: Symbol is not a constructor

// 但可以用 Object() 显式装箱
const wrappedSym = Object(sym);
console.log(typeof wrappedSym); // "object"
console.log(wrappedSym instanceof Symbol); // true

13.5 实战

13.5.1 实战1:金额格式化

实现一个金额格式化工具,支持千分位分隔、小数保留、货币符号。

javascript 复制代码
/**
 * 金额格式化工具
 */
class MoneyFormatter {
  /**
   * @param {string} currency - 货币符号
   * @param {number} decimals - 小数位数
   */
  constructor(currency = '¥', decimals = 2) {
    this.currency = currency;
    this.decimals = decimals;
  }

  /**
   * 格式化金额为千分位字符串
   * @param {number} amount - 金额
   * @returns {string} 格式化后的金额字符串
   */
  format(amount) {
    if (!Number.isFinite(amount)) {
      return `${this.currency}0.${'0'.repeat(this.decimals)}`;
    }

    // 精确舍入
    const factor = Math.pow(10, this.decimals);
    const rounded = Math.round((amount + Number.EPSILON) * factor) / factor;

    // 分离整数和小数部分
    const [intPart, decPart] = rounded.toFixed(this.decimals).split('.');

    // 千分位分隔
    const formattedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');

    return `${this.currency}${formattedInt}.${decPart}`;
  }

  /**
   * 解析金额字符串为数字
   * @param {string} str - 金额字符串
   * @returns {number} 解析后的金额
   */
  parse(str) {
    const cleaned = str.replace(/[¥$,\s]/g, '');
    const num = parseFloat(cleaned);
    return Number.isFinite(num) ? num : 0;
  }
}

// ===== 测试 =====

const formatter = new MoneyFormatter('¥', 2);

console.log(formatter.format(1234567.89));   // "¥1,234,567.89"
console.log(formatter.format(999.999));      // "¥1,000.00"(进位)
console.log(formatter.format(0.1));          // "¥0.10"
console.log(formatter.format(-1234.5));      // "¥-1,234.50"
console.log(formatter.format(0));            // "¥0.00"

// 解析
console.log(formatter.parse('¥1,234,567.89')); // 1234567.89
console.log(formatter.parse('$1,000.00'));     // 1000

// 使用 Intl.NumberFormat(浏览器原生方案)
const intlFormatter = new Intl.NumberFormat('zh-CN', {
  style: 'currency',
  currency: 'CNY',
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

console.log(intlFormatter.format(1234567.89)); // "¥1,234,567.89"
console.log(intlFormatter.format(0.1));         // "¥0.10"

13.5.2 实战2:精度安全计算

实现一个精确的加减乘除运算工具,解决浮点数精度问题。

javascript 复制代码
/**
 * 精度安全计算工具
 * 解决 JavaScript 浮点数精度问题
 */
const Decimal = {
  /**
   * 获取数字的小数位数
   * @param {number} num
   * @returns {number}
   */
  _getDecimals(num) {
    const str = num.toString();
    const scientific = str.indexOf('e-');
    if (scientific !== -1) {
      return parseInt(str.slice(scientific + 2));
    }
    const dotIndex = str.indexOf('.');
    return dotIndex === -1 ? 0 : str.length - dotIndex - 1;
  },

  /**
   * 将浮点数转换为整数(保留精度)
   * @param {number} num
   * @returns {{ value: number, decimals: number }}
   */
  _toInt(num) {
    const decimals = this._getDecimals(num);
    const str = num.toFixed(decimals);
    return {
      value: parseInt(str.replace('.', '')),
      decimals
    };
  },

  /**
   * 精确加法
   * @param {number} a
   * @param {number} b
   * @returns {number}
   */
  add(a, b) {
    const aInt = this._toInt(a);
    const bInt = this._toInt(b);
    const maxDecimals = Math.max(aInt.decimals, bInt.decimals);
    const factor = Math.pow(10, maxDecimals);

    const aScaled = aInt.value * Math.pow(10, maxDecimals - aInt.decimals);
    const bScaled = bInt.value * Math.pow(10, maxDecimals - bInt.decimals);

    return (aScaled + bScaled) / factor;
  },

  /**
   * 精确减法
   * @param {number} a
   * @param {number} b
   * @returns {number}
   */
  subtract(a, b) {
    return this.add(a, -b);
  },

  /**
   * 精确乘法
   * @param {number} a
   * @param {number} b
   * @returns {number}
   */
  multiply(a, b) {
    const aInt = this._toInt(a);
    const bInt = this._toInt(b);

    const result = aInt.value * bInt.value;
    const factor = Math.pow(10, aInt.decimals + bInt.decimals);

    return result / factor;
  },

  /**
   * 精确除法
   * @param {number} a
   * @param {number} b
   * @returns {number}
   */
  divide(a, b) {
    if (b === 0) {
      throw new Error('除数不能为零');
    }
    const aInt = this._toInt(a);
    const bInt = this._toInt(b);

    const result = (aInt.value / bInt.value);
    const factor = Math.pow(10, aInt.decimals - bInt.decimals);

    return result / factor;
  },

  /**
   * 精确四舍五入
   * @param {number} num
   * @param {number} digits
   * @returns {number}
   */
  round(num, digits = 0) {
    const factor = Math.pow(10, digits);
    return Math.round((num + Number.EPSILON) * factor) / factor;
  }
};

// ===== 测试 =====

// 加法
console.log(Decimal.add(0.1, 0.2));        // 0.3(原生 0.30000000000000004)
console.log(Decimal.add(19.9, 0.1));       // 20(原生 19.999999999999996)
console.log(Decimal.add(0.001, 0.002));    // 0.003

// 减法
console.log(Decimal.subtract(0.3, 0.1));    // 0.2(原生 0.19999999999999998)
console.log(Decimal.subtract(1.5, 1.2));   // 0.3

// 乘法
console.log(Decimal.multiply(0.1, 3));     // 0.3(原生 0.30000000000000004)
console.log(Decimal.multiply(19.9, 100));  // 1990(原生 1989.9999999999998)
console.log(Decimal.multiply(0.07, 100));  // 7

// 除法
console.log(Decimal.divide(0.3, 0.1));     // 3(原生 2.9999999999999996)
console.log(Decimal.divide(1, 3));         // 0.3333333333333333

// 四舍五入
console.log(Decimal.round(1.005, 2));      // 1.01(原生 toFixed 得到 "1.00")
console.log(Decimal.round(1.025, 2));      // 1.03
console.log(Decimal.round(2.5, 0));        // 3

13.5.3 实战3:进制转换工具

javascript 复制代码
/**
 * 进制转换工具
 * 支持 2~36 进制之间的互相转换
 */
class RadixConverter {
  /**
   * 十进制转其他进制
   * @param {number} decimal - 十进制数字
   * @param {number} radix - 目标进制(2~36)
   * @returns {string} 转换后的字符串
   */
  static fromDecimal(decimal, radix) {
    if (!Number.isInteger(decimal)) {
      throw new Error('只支持整数转换');
    }
    if (radix < 2 || radix > 36) {
      throw new Error('进制范围必须在 2~36 之间');
    }
    return decimal.toString(radix).toUpperCase();
  }

  /**
   * 其他进制转十进制
   * @param {string} str - 源字符串
   * @param {number} radix - 源进制(2~36)
   * @returns {number} 十进制数字
   */
  static toDecimal(str, radix) {
    const result = parseInt(str, radix);
    if (Number.isNaN(result)) {
      throw new Error(`"${str}" 不是合法的 ${radix} 进制数`);
    }
    return result;
  }

  /**
   * 任意进制互转
   * @param {string} str - 源字符串
   * @param {number} fromRadix - 源进制
   * @param {number} toRadix - 目标进制
   * @returns {string} 转换后的字符串
   */
  static convert(str, fromRadix, toRadix) {
    const decimal = this.toDecimal(str, fromRadix);
    return this.fromDecimal(decimal, toRadix);
  }

  /**
   * 获取数字的完整进制信息
   * @param {number} num - 十进制数字
   * @returns {Object} 包含各种进制的对象
   */
  static getInfo(num) {
    return {
      decimal: num.toString(),
      binary: num.toString(2),
      octal: num.toString(8),
      hex: num.toString(16).toUpperCase(),
      base36: num.toString(36).toUpperCase()
    };
  }
}

// ===== 测试 =====

// 十进制转其他进制
console.log(RadixConverter.fromDecimal(255, 2));   // "11111111"
console.log(RadixConverter.fromDecimal(255, 8));   // "377"
console.log(RadixConverter.fromDecimal(255, 16));  // "FF"
console.log(RadixConverter.fromDecimal(255, 36));   // "73"

// 其他进制转十进制
console.log(RadixConverter.toDecimal('11111111', 2)); // 255
console.log(RadixConverter.toDecimal('FF', 16));      // 255

// 任意进制互转
console.log(RadixConverter.convert('FF', 16, 2));   // "11111111"(十六进制 → 二进制)
console.log(RadixConverter.convert('11111111', 2, 16)); // "FF"(二进制 → 十六进制)
console.log(RadixConverter.convert('73', 36, 10));  // "255"(三十六进制 → 十进制)

// 获取完整进制信息
console.log(RadixConverter.getInfo(255));
// { decimal: "255", binary: "11111111", octal: "377", hex: "FF", base36: "73" }

13.5.4 实战4:大数计算器

利用 BigInt 实现大整数的阶乘、幂运算和组合数计算。

javascript 复制代码
/**
 * 大数计算器
 * 基于 BigInt 实现超大整数运算
 */
class BigCalculator {
  /**
   * 阶乘 n!
   * @param {number} n - 非负整数
   * @returns {bigint} n 的阶乘
   */
  static factorial(n) {
    if (n < 0) {
      throw new Error('阶乘只支持非负整数');
    }
    if (n === 0 || n === 1) {
      return 1n;
    }
    let result = 1n;
    for (let i = 2n; i <= BigInt(n); i++) {
      result *= i;
    }
    return result;
  }

  /**
   * 幂运算 base^exp
   * @param {bigint} base - 底数
   * @param {number} exp - 指数(非负整数)
   * @returns {bigint}
   */
  static power(base, exp) {
    if (exp < 0) {
      throw new Error('指数只支持非负整数');
    }
    if (exp === 0) {
      return 1n;
    }

    // 快速幂算法
    let result = 1n;
    let b = BigInt(base);
    let e = BigInt(exp);

    while (e > 0n) {
      if (e % 2n === 1n) {
        result *= b;
      }
      b *= b;
      e /= 2n;
    }

    return result;
  }

  /**
   * 组合数 C(n, k)
   * @param {number} n
   * @param {number} k
   * @returns {bigint}
   */
  static combination(n, k) {
    if (k < 0 || k > n) {
      return 0n;
    }
    if (k === 0 || k === n) {
      return 1n;
    }
    // C(n, k) = C(n, n-k),取较小的 k 提高效率
    if (k > n - k) {
      k = n - k;
    }

    let result = 1n;
    for (let i = 0; i < k; i++) {
      result *= BigInt(n - i);
      result /= BigInt(i + 1);
    }
    return result;
  }

  /**
   * 斐波那契数列(大数版)
   * @param {number} n - 第 n 项
   * @returns {bigint}
   */
  static fibonacci(n) {
    if (n < 0) {
      throw new Error('只支持非负整数');
    }
    if (n <= 1) {
      return BigInt(n);
    }

    let prev = 0n;
    let curr = 1n;
    for (let i = 2; i <= n; i++) {
      const next = prev + curr;
      prev = curr;
      curr = next;
    }
    return curr;
  }

  /**
   * 格式化大数输出(千分位)
   * @param {bigint} num
   * @returns {string}
   */
  static format(num) {
    const str = num.toString();
    const isNegative = str.startsWith('-');
    const digits = isNegative ? str.slice(1) : str;
    const formatted = digits.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    return isNegative ? `-${formatted}` : formatted;
  }
}

// ===== 测试 =====

// 阶乘(普通 number 会溢出)
console.log(BigCalculator.factorial(20).toString());
// "2432902008176640000"

console.log(BigCalculator.factorial(50).toString());
// "30414093201713378043612608166064768844377641568960512000000000000"

console.log(BigCalculator.format(BigCalculator.factorial(50)));
// "30,414,093,201,713,378,043,612,608,166,064,768,844,377,641,568,960,512,000,000,000,000"

// 幂运算
console.log(BigCalculator.power(2n, 100).toString());
// "1267650600228229401496703205376"(2^100,远超 Number.MAX_SAFE_INTEGER)

// 组合数
console.log(BigCalculator.combination(100, 50).toString());
// "100891344545564193334812497256"

// 斐波那契
console.log(BigCalculator.fibonacci(100).toString());
// "354224848179261915075"

console.log(BigCalculator.format(BigCalculator.fibonacci(100)));
// "354,224,848,179,261,915,075"

13.5.5 实战5:数字类型检测与安全转换

javascript 复制代码
/**
 * 数字安全工具
 * 提供严格的数字检测和安全转换
 */
const SafeNumber = {
  /**
   * 检查值是否为有限数字
   * @param {*} value
   * @returns {boolean}
   */
  isNumber(value) {
    return typeof value === 'number' && Number.isFinite(value);
  },

  /**
   * 检查值是否为安全整数
   * @param {*} value
   * @returns {boolean}
   */
  isSafeInteger(value) {
    return typeof value === 'number' && Number.isSafeInteger(value);
  },

  /**
   * 安全转换为数字
   * @param {*} value - 要转换的值
   * @param {number} [fallback=0] - 转换失败时的默认值
   * @returns {number}
   */
  toNumber(value, fallback = 0) {
    if (typeof value === 'number') {
      return Number.isFinite(value) ? value : fallback;
    }
    if (typeof value === 'string') {
      const trimmed = value.trim();
      if (trimmed === '') return fallback;
      const num = Number(trimmed);
      return Number.isFinite(num) ? num : fallback;
    }
    if (typeof value === 'boolean') {
      return value ? 1 : 0;
    }
    if (value === null) {
      return 0;
    }
    if (value === undefined) {
      return fallback;
    }
    if (typeof value === 'bigint') {
      if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
        console.warn('BigInt 超出安全范围,转换可能丢失精度');
      }
      return Number(value);
    }
    return fallback;
  },

  /**
   * 安全转换为整数
   * @param {*} value
   * @param {number} [fallback=0]
   * @returns {number}
   */
  toInteger(value, fallback = 0) {
    const num = this.toNumber(value, NaN);
    if (Number.isNaN(num)) return fallback;
    return Math.trunc(num);
  },

  /**
   * 将数字限制在指定范围内
   * @param {number} value
   * @param {number} min
   * @param {number} max
   * @returns {number}
   */
  clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  },

  /**
   * 生成指定范围内的随机整数
   * @param {number} min
   * @param {number} max
   * @returns {number}
   */
  randomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  },

  /**
   * 数字简写格式(K、M、B)
   * @param {number} num
   * @returns {string}
   */
  abbreviate(num) {
    if (!this.isNumber(num)) return '0';
    const abs = Math.abs(num);
    if (abs >= 1e9) return (num / 1e9).toFixed(1) + 'B';
    if (abs >= 1e6) return (num / 1e6).toFixed(1) + 'M';
    if (abs >= 1e3) return (num / 1e3).toFixed(1) + 'K';
    return num.toString();
  }
};

// ===== 测试 =====

// 检测
console.log(SafeNumber.isNumber(42));       // true
console.log(SafeNumber.isNumber(NaN));      // false
console.log(SafeNumber.isNumber('42'));      // false
console.log(SafeNumber.isSafeInteger(42));   // true
console.log(SafeNumber.isSafeInteger(9007199254740992)); // false

// 安全转换
console.log(SafeNumber.toNumber('42'));       // 42
console.log(SafeNumber.toNumber('42px'));     // 0(NaN → fallback)
console.log(SafeNumber.toNumber('42px', -1)); // -1(自定义 fallback)
console.log(SafeNumber.toNumber(''));         // 0
console.log(SafeNumber.toNumber(null));      // 0
console.log(SafeNumber.toNumber(undefined)); // 0
console.log(SafeNumber.toNumber(true));      // 1
console.log(SafeNumber.toNumber(100n));       // 100

// 整数转换
console.log(SafeNumber.toInteger('42.9'));   // 42
console.log(SafeNumber.toInteger('-3.7'));   // -3
console.log(SafeNumber.toInteger('abc'));     // 0

// 范围限制
console.log(SafeNumber.clamp(15, 0, 10));    // 10
console.log(SafeNumber.clamp(-5, 0, 10));    // 0
console.log(SafeNumber.clamp(5, 0, 10));     // 5

// 随机整数
console.log(SafeNumber.randomInt(1, 100));   // 1~100 的随机整数

// 数字简写
console.log(SafeNumber.abbreviate(999));       // "999"
console.log(SafeNumber.abbreviate(1234));      // "1.2K"
console.log(SafeNumber.abbreviate(1000000));   // "1.0M"
console.log(SafeNumber.abbreviate(1500000000)); // "1.5B"

13.6 原始类型方法速查表

13.6.1 数字方法速查

方法 说明 示例 返回
toFixed(n) 保留 n 位小数 (3.14159).toFixed(2) "3.14"
toString(radix) 转为指定进制字符串 (255).toString(16) "ff"
toPrecision(n) 保留 n 位有效数字 (123.456).toPrecision(3) "123"
toExponential(n) 科学计数法 (12345).toExponential(2) "1.23e+4"
Number.isInteger(x) 是否为整数 Number.isInteger(42) true
Number.isFinite(x) 是否为有限数 Number.isFinite(42) true
Number.isNaN(x) 是否为 NaN Number.isNaN(NaN) true
Number.isSafeInteger(x) 是否为安全整数 Number.isSafeInteger(2**53) false
parseInt(str, radix) 字符串转整数 parseInt('ff', 16) 255
parseFloat(str) 字符串转浮点数 parseFloat('3.14') 3.14

13.6.2 Number 静态常量速查

常量 说明
Number.MAX_SAFE_INTEGER 2 53 − 1 2^{53} - 1 253−1 最大安全整数
Number.MIN_SAFE_INTEGER − ( 2 53 − 1 ) -(2^{53} - 1) −(253−1) 最小安全整数
Number.MAX_VALUE ≈ 1.8 × 10 308 \approx 1.8 \times 10^{308} ≈1.8×10308 最大正数
Number.MIN_VALUE ≈ 5 × 10 − 324 \approx 5 \times 10^{-324} ≈5×10−324 最小正数
Number.EPSILON ≈ 2.22 × 10 − 16 \approx 2.22 \times 10^{-16} ≈2.22×10−16 浮点数最小精度
Number.POSITIVE_INFINITY Infinity 正无穷
Number.NEGATIVE_INFINITY -Infinity 负无穷
Number.NaN NaN 非数字

13.6.3 转换方法对比

方法 输入 输出 特点
Number(str) '42px' NaN 严格,整体必须合法
parseInt(str, 10) '42px' 42 宽松,逐字符解析
parseFloat(str) '3.14px' 3.14 宽松,支持小数
+str '42' 42 等价于 Number()
str * 1 '42' 42 等价于 Number()
BigInt(str) '9007199254740993' 9007199254740993n 精确大整数

本章小结

知识点 要点
装箱机制 原始类型调用方法时,引擎临时创建包装对象,方法执行后立即销毁
null / undefined 没有包装对象,调用方法会报 TypeError
包装对象 不要手动 new Number() / new String() / new Boolean()
toFixed(n) 保留 n 位小数,返回字符串;存在精度陷阱
toString(radix) 转换为指定进制字符串,支持 2~36 进制
parseInt(str, radix) 从字符串解析整数,建议始终传入 radix
Number() vs parseInt() 前者严格(整体合法),后者宽松(逐字符解析)
Number.isNaN / Number.isFinite 不做类型转换,比全局 isNaN / isFinite 更安全
IEEE 754 所有 number 都是 64 位浮点数,导致精度问题
0.1 + 0.2 !== 0.3 浮点数精度问题的经典案例,用 Number.EPSILON 比较
Number.MAX_SAFE_INTEGER 2 53 − 1 2^{53} - 1 253−1,超过此范围整数精度丢失
BigInt 任意精度整数,用 n 后缀或 BigInt() 创建;不能与 number 混合运算

一句话总结 :JavaScript 的原始类型通过隐式"装箱"机制获得方法调用能力,但包装对象是临时的、不可持久的;数字类型的方法(toFixedtoStringparseInt)在日常开发中极为常用,而 IEEE 754 浮点数的精度问题需要通过整数运算、Number.EPSILONBigInt 来规避。


下篇预告第14章:字符串 --- 我们将深入 JavaScript 字符串的完整方法体系,学习模板字面量、标签模板、Unicode 处理,以及如何构建简易模板引擎。


作者 :xhmico | CSDN 博客https://blog.csdn.net/xhmico

参考资源The Modern JavaScript Tutorial | MDN Web Docs | ECMAScript 规范

相关推荐
এ慕ོ冬℘゜1 小时前
深入理解 JavaScript 事件体系:Window、鼠标与键盘事件详解
开发语言·javascript·okhttp
摇滚侠3 小时前
SpringBoot3+Vue3 全套视频教程 45-51
前端·javascript·vue.js
chouchuang3 小时前
day-030-综合练习-笔记管理器
开发语言·笔记·python
触底反弹3 小时前
🔥 RAG 到底是怎么工作的?掰开揉碎了给你讲明白!
javascript·人工智能·后端
Hilaku3 小时前
前端大裁员背后的恐慌:我们还剩什么底牌?
前端·javascript·程序员
云空4 小时前
《Three.js 3D实例大全》
开发语言·javascript·3d·three.js
techdashen4 小时前
Go 1.26 新增 `bytes.Buffer.Peek`:只看数据,不移动读取位置
开发语言·后端·golang
C137的本贾尼4 小时前
第七篇:消息队列(MQ)——就是个带存储的异步通信管道
java·开发语言·中间件
云空4 小时前
《Three.js 3D坦克对战【AI单机版】》
javascript·人工智能·3d·three.js