前言

在鸿蒙应用开发中,ArkTS 基于 TypeScript,很多初学者在写原生代码时,经常会遇到一些看似简单却容易踩坑的小问题。本文总结了分支判断、赋值与等于、常用内置对象、参数写法、异常处理、字符串操作、小数与类型转换七个方面的常见误区,每个知识点都拆解得非常细致,包含大量代码示例和边界情况说明,帮助新手彻底理解背后的原理。

一、分支语句:if 与 if-else 的区别详解

1.1 if 单独使用

if 语句单独使用时,条件为 true 就执行代码块,为 false 就跳过。

let age = 18;
if (age >= 18) {
    console.log("已成年");
}
// 条件为 true,输出:已成年

let score = 60;
if (score >= 90) {
    console.log("优秀");
}
// 条件为 false,不输出任何内容

特点:不处理条件为 false 的情况,直接跳过。

1.2 if-else 配对使用

if-else 提供了二选一的分支:条件为 true 执行 if 块,为 false 执行 else 块。

let age = 16;
if (age >= 18) {
    console.log("已成年");
} else {
    console.log("未成年");
}
// 输出:未成年

特点:一定会执行其中一个分支,不会跳过。

1.3 if-else if-else 多分支

用于多个条件按优先级判断,从上到下匹配,一旦匹配成功,后面的不再判断

let score = 85;

if (score >= 90) {
    console.log("优秀");      // 不执行
} else if (score >= 80) {
    console.log("良好");      // ✅ 执行这个
} else if (score >= 70) {
    console.log("中等");      // 不执行(前面已匹配)
} else {
    console.log("需努力");    // 不执行
}
// 输出:良好

关键点:即使 score = 85 同时满足 score >= 70score >= 80,也只会执行第一个匹配的 score >= 80 分支。条件按顺序判断,命中即停止。

1.4 多个独立 if 并列

多个独立的 if 语句,每个都会独立判断,可能执行多个

let x = 8;

if (x > 0) {
    console.log("正数");      // ✅ 执行
}
if (x > 3) {
    console.log("大于3");     // ✅ 执行
}
if (x > 10) {
    console.log("大于10");    // ❌ 不执行
}
if (x % 2 === 0) {
    console.log("偶数");      // ✅ 执行
}
// 输出:正数  大于3  偶数

1.5 核心区别总结与避坑示例

写法 判断次数 最多执行分支数 适用场景
if-else if-else 直到匹配为止 1 个 单选:成绩等级、权限判断
多个独立 if 全部判断 多个 多选:同时校验多个条件

避坑示例

// ❌ 错误:想实现单选却用了多个独立 if
let grade = 85;
if (grade >= 60) {
    console.log("及格");
}
if (grade >= 80) {
    console.log("良好");
}
// grade = 85 时会同时输出 "及格" 和 "良好"(这是问题所在!)

// ✅ 正确:使用 else if
if (grade >= 80) {
    console.log("良好");
} else if (grade >= 60) {
    console.log("及格");
}
// grade = 85 只输出 "良好"

二、赋值与等于:=、==、 === 及真假值详解

2.1 赋值运算符 =

= 用于给变量赋值,不是比较

let a = 5;        // 把 5 赋给 a
let b = a;        // 把 a 的值赋给 b

常见错误:在 if 条件中误用 = 而不是 ===

let x = 10;
// ❌ 错误:这是赋值,不是比较
if (x = 5) {      // 会把 5 赋给 x,整个表达式值为 5(true),条件永远为真
    console.log("条件成立");
}
// ✅ 正确
if (x === 5) {
    console.log("x 等于 5");
}

2.2 相等运算符 ==(松散相等)

== 会在比较前进行类型转换,再比较值是否相等。

console.log(5 == "5");        // true   (字符串 "5" 转成数字 5)
console.log(0 == false);      // true   (false 转成 0)
console.log(null == undefined); // true (特殊规则)
console.log("" == false);     // true   (都转成 0)
console.log([] == false);     // true   (空数组转成 "" 再转 0)

2.3 全等运算符 ===(严格相等)

=== 不进行类型转换,类型不同直接返回 false

console.log(5 === "5");       // false  (类型不同:数字 vs 字符串)
console.log(0 === false);     // false  (数字 vs 布尔)
console.log(null === undefined); // false (类型不同)
console.log("" === false);    // false
console.log(NaN === NaN);    // 特别注意:此时结果为 false要用 isNaN()判断

2.4 == 和 === 的完整对比表

比较 == 结果 === 结果
5 == "5" ✅ true ❌ false
0 == false ✅ true ❌ false
null == undefined ✅ true ❌ false
"" == false ✅ true ❌ false
"abc" == "abc" ✅ true ✅ true
5 == 5 ✅ true ✅ true
NaN == NaN ❌ false ❌ false

建议:始终使用 ===!==,避免因隐式类型转换导致难以排查的 bug。

2.5 真假值(Truthy 和 Falsy)完整列表

ifwhile&&|| 等需要布尔值的上下文中,值会被自动转换为布尔类型。

假值(Falsy) — 转为 false 的值(共 8 个):

说明
false 布尔假
0 数字零
-0 负零
0n BigInt 零
"" 空字符串
null 空值
undefined 未定义
NaN 非数字

真值(Truthy) — 除以上 8 个值外,所有其他值都是 true

// 这些容易被误认为 false,实际是 true
if ("0") console.log("执行");      // ✅ 非空字符串
if ("false") console.log("执行");   // ✅ 非空字符串
if ([]) console.log("执行");        // ✅ 空数组
if ({}) console.log("执行");        // ✅ 空对象
if (-1) console.log("执行");        // ✅ 非零数字

2.6 常见陷阱与最佳实践

陷阱 1:判断数组是否为空

let arr = [];
// ❌ 错误:空数组是 true,这个判断永远不会进入 else
if (arr) {
    console.log("数组有元素");  // 实际上会执行这里!
} else {
    console.log("数组为空");
}
// ✅ 正确
if (arr.length > 0) {
    console.log("数组有元素");
} else {
    console.log("数组为空");
}

陷阱 2:判断变量是否为 null 或 undefined

let value;
// ✅ 方式一:显式判断
if (value === null || value === undefined) {
    console.log("值为空");
}
// ✅ 方式二:利用 == null(同时匹配 null 和 undefined)
if (value == null) {
    console.log("值为空");  // value 为 null 或 undefined 时进入
}
// ❌ 注意:if (!value) 会把 0、""、false 也当作空

三、常用内置对象:Math 详解

3.1 Math 对象概述与常数

Math 是内置对象,提供数学常数和函数,不能作为函数调用,不能 new

console.log(Math.PI);    // 3.141592653589793
console.log(Math.E);     // 2.718281828459045
console.log(Math.LN10);  // 2.302585092994046
console.log(Math.SQRT2); // 1.4142135623730951

3.2 取整方法详解(floor/ceil/round/trunc)

方法 作用 示例 结果
Math.floor(x) 向下取整(向小取) Math.floor(3.9) 3
Math.floor(-3.1) -4
Math.ceil(x) 向上取整(向大取) Math.ceil(3.1) 4
Math.ceil(-3.9) -3
Math.round(x) 四舍五入 Math.round(3.4) 3
Math.round(3.5) 4
Math.round(-3.5) -3(注意:-3.5 四舍五入为 -3)
Math.trunc(x) 直接去掉小数部分 Math.trunc(3.9) 3
Math.trunc(-3.1) -3
// 实际应用:保留两位小数(截断方式,不是四舍五入)
let price = 3.4567;
let truncated = Math.floor(price * 100) / 100;  // 3.45
let rounded = Math.round(price * 100) / 100;   // 3.46

3.3 随机数生成(含随机整数封装)

Math.random() 返回 [0, 1) 的随机浮点数(包含 0,不包含 1)。

// 基础用法
let r = Math.random();      // 0.000... 到 0.999...

// 生成 [0, max) 的随机整数
function getRandomInt(max: number): number {
    return Math.floor(Math.random() * max);
}

// 生成 [min, max] 的随机整数(包含两端)
function getRandomIntInRange(min: number, max: number): number {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

console.log(getRandomIntInRange(1, 100));  // 1~100 随机整数

3.4 最大值/最小值与数组展开

let max = Math.max(1, 5, 3, 9, 2);    // 9
let min = Math.min(1, 5, 3, 9, 2);    // 1

// 对数组求最大/最小值
let arr = [10, 20, 30, 5];
let arrMax = Math.max(...arr);        // 30(使用展开运算符)
let arrMin = Math.min(...arr);        // 5

3.5 常用数学运算方法

方法 作用 示例
Math.abs(x) 绝对值 Math.abs(-5) → 5
Math.pow(x, y) x 的 y 次方 Math.pow(2, 3) → 8
Math.sqrt(x) 平方根 Math.sqrt(16) → 4
Math.cbrt(x) 立方根 Math.cbrt(27) → 3
Math.hypot(x, y) 平方和的平方根 Math.hypot(3, 4) → 5
// 实际应用:计算两点距离
function distance(x1: number, y1: number, x2: number, y2: number): number {
    return Math.hypot(x2 - x1, y2 - y1);
}

四、常用内置对象:Date 详解

4.1 创建 Date 对象的多种方式

// 方式1:当前时间
let now = new Date();

// 方式2:时间戳(毫秒)
let fromTimestamp = new Date(1700000000000);

// 方式3:日期字符串
let fromString = new Date("2026-05-20T14:30:00");

// 方式4:年、月、日、时、分、秒、毫秒
// 注意:月份从 0 开始(0=一月,11=十二月)
let fromParams = new Date(2026, 4, 20, 14, 30, 0, 0);
// 2026年5月20日 14:30:00

4.2 获取年月日时分秒(含UTC)

let date = new Date();

// 本地时间
let year = date.getFullYear();      // 2026
let month = date.getMonth();        // 4(实际是5月,需要+1)
let day = date.getDate();           // 1-31
let weekday = date.getDay();        // 0-6(0=周日)
let hours = date.getHours();        // 0-23
let minutes = date.getMinutes();    // 0-59
let seconds = date.getSeconds();    // 0-59
let milliseconds = date.getMilliseconds(); // 0-999

// UTC 时间(世界协调时)
let utcHours = date.getUTCHours();
let utcMonth = date.getUTCMonth();

4.3 设置日期时间

let date = new Date();

date.setFullYear(2025);
date.setMonth(11);        // 12月(月份0-11)
date.setDate(25);         // 25日
date.setHours(10);
date.setMinutes(30);
date.setSeconds(0);
date.setMilliseconds(0);

4.4 时间戳操作与日期差计算

let date = new Date();

// 获取时间戳(毫秒,从1970-01-01开始)
let timestamp = date.getTime();      // 方式1
let timestamp2 = +date;              // 方式2(一元加号)
let timestamp3 = Date.now();         // 方式3(静态方法,无需 new)

// 时间戳转 Date
let fromTs = new Date(timestamp);

// 计算两个日期之间的天数差
let start = new Date(2026, 0, 1);    // 2026-01-01
let end = new Date(2026, 0, 10);     // 2026-01-10
let diffMs = end.getTime() - start.getTime();  // 777600000 毫秒
let diffDays = diffMs / (1000 * 60 * 60 * 24); // 9 天

4.5 日期格式化(含自定义格式化函数)

let date = new Date();

// 内置格式化
console.log(date.toLocaleDateString());   // "2026/5/20"
console.log(date.toLocaleTimeString());   // "14:30:00"
console.log(date.toLocaleString());       // "2026/5/20 14:30:00"
console.log(date.toISOString());          // "2026-05-20T06:30:00.000Z"

// 自定义格式化函数
function formatDate(date: Date, format: string = "YYYY-MM-DD"): string {
    let y = date.getFullYear();
    let m = String(date.getMonth() + 1).padStart(2, "0");
    let d = String(date.getDate()).padStart(2, "0");
    let h = String(date.getHours()).padStart(2, "0");
    let min = String(date.getMinutes()).padStart(2, "0");
    let s = String(date.getSeconds()).padStart(2, "0");
    
    return format
        .replace("YYYY", String(y))
        .replace("MM", m)
        .replace("DD", d)
        .replace("HH", h)
        .replace("mm", min)
        .replace("ss", s);
}
console.log(formatDate(new Date(), "YYYY年MM月DD日 HH:mm:ss"));

4.6 日期计算与比较(加N天、相差天数)

// 日期比较(直接用 > < == 比较时间戳)
let d1 = new Date(2026, 0, 1);
let d2 = new Date(2026, 0, 15);
console.log(d1 < d2);   // true

// 计算 N 天后的日期
function addDays(date: Date, days: number): Date {
    let result = new Date(date);
    result.setDate(result.getDate() + days);
    return result;
}

// 计算两个日期之间的天数差(绝对值)
function daysBetween(date1: Date, date2: Date): number {
    const msPerDay = 1000 * 60 * 60 * 24;
    const diffMs = Math.abs(date2.getTime() - date1.getTime());
    return Math.floor(diffMs / msPerDay);
}

五、函数参数:可选参数与剩余参数详解

5.1 可选参数(?)与参数默认值对比

在参数名后加 ? 表示可选,未传值时值为 undefined可选参数必须放在必选参数之后

// 可选参数版本
function buildName(firstName: string, lastName?: string): string {
    if (lastName) {
        return `${firstName} ${lastName}`;
    } else {
        return firstName;
    }
}
console.log(buildName("张三"));      // "张三"
console.log(buildName("李", "四"));  // "李 四"

// 参数默认值版本(推荐)
function greet(name: string = "Anonymous"): string {
    return `Hello ${name}`;
}
console.log(greet());        // "Hello Anonymous"
console.log(greet("张三"));  // "Hello 张三"

5.2 剩余参数(…rest)

用于接收不定数量的参数,以数组形式保存。剩余参数必须放在最后

// 求和:接收任意多个数字
function sum(...numbers: number[]): number {
    let total = 0;
    for (let num of numbers) {
        total += num;
    }
    return total;
}
console.log(sum(1, 2, 3, 4, 5));  // 15
console.log(sum());               // 0

// 混合使用
function multiply(base: number, ...factors: number[]): number[] {
    return factors.map(factor => base * factor);
}
console.log(multiply(2, 3, 5, 7));   // [6, 10, 14]

5.3 可选参数与剩余参数混合使用

function logMessage(prefix: string, suffix?: string, ...messages: string[]): void {
    for (let msg of messages) {
        if (suffix) {
            console.log(`${prefix}: ${msg}${suffix}`);
        } else {
            console.log(`${prefix}: ${msg}`);
        }
    }
}
logMessage("INFO", "!", "登录成功", "数据加载完成");
// INFO: 登录成功!
// INFO: 数据加载完成!

六、异常处理:try/catch/finally 与自定义错误详解

6.1 try-catch-finally 基本语法

try {
    // 可能抛出异常的代码
    let result = riskyOperation();
    console.log("执行成功:", result);
} catch (error) {
    // 捕获并处理错误
    console.error("出错了:", error.message);
} finally {
    // 无论是否出错都会执行(可选)
    console.log("清理工作");
}

6.2 错误对象 Error 详解

catch 捕获到的 error 是 Error 类型,包含以下属性:

try {
    throw new Error("自定义错误信息");
} catch (err) {
    console.log(err.name);      // "Error"
    console.log(err.message);   // "自定义错误信息"
    console.log(err.stack);     // 调用堆栈信息(调试用)
}

6.3 抛出自定义错误(throw)

使用 throw 主动抛出异常,可以抛出任何值,但建议抛出 Error 对象。

function divide(a: number, b: number): number {
    if (b === 0) {
        throw new Error("除数不能为0");
    }
    return a / b;
}

try {
    let result = divide(10, 0);
} catch (err) {
    console.log(err.message);   // "除数不能为0"
}

6.4 自定义错误类(继承Error)

// 基础业务错误类
class BusinessError extends Error {
    public code: number;
    constructor(code: number, message: string) {
        super(message);
        this.name = "BusinessError";
        this.code = code;
    }
}

// 具体错误类型
class ValidationError extends BusinessError {
    constructor(message: string) {
        super(1001, message);
        this.name = "ValidationError";
    }
}

class NetworkError extends BusinessError {
    constructor(message: string) {
        super(2001, message);
        this.name = "NetworkError";
    }
}

// 使用示例
function validateAge(age: number): void {
    if (age < 0) {
        throw new ValidationError("年龄不能为负数");
    }
    if (age > 150) {
        throw new ValidationError("年龄超出合理范围");
    }
}

try {
    validateAge(-5);
} catch (err) {
    if (err instanceof ValidationError) {
        console.log(`校验错误 [${err.code}]: ${err.message}`);
    } else if (err instanceof NetworkError) {
        console.log(`网络错误 [${err.code}]: ${err.message}`);
    } else {
        console.log("未知错误", err);
    }
}

6.5 finally 的典型使用场景

// 场景1:资源释放
let isLoading = true;
try {
    await fetchData();
} finally {
    isLoading = false;  // 无论成功还是失败,都结束加载状态
}

// 场景2:文件操作(伪代码)
function readFile(path: string): string {
    let file = openFile(path);
    try {
        return file.read();
    } finally {
        file.close();  // 确保文件被关闭
    }
}

6.6 异常处理最佳实践

// 1. 只捕获和处理你能处理的异常
try {
    let data = JSON.parse(jsonString);
} catch (err) {
    console.error("JSON解析失败:", err);
    throw new Error("数据格式错误");  // 重新抛出
}

// 2. 不要吞掉异常
try {
    riskyOp();
} catch (err) {
    // ❌ 不要这样:什么都不做
}

// 3. 异步函数异常处理
async function fetchData(): Promise<void> {
    try {
        let response = await fetch("/api/data");
        let data = await response.json();
    } catch (err) {
        console.error("请求失败:", err);
    }
}

七、字符串常用操作详解

7.1 去除空格(trim/trimStart/trimEnd)

方法 作用 示例
trim() 去除首尾空格 " hello ".trim()"hello"
trimStart() 去除开头空格 " hello ".trimStart()"hello "
trimEnd() 去除结尾空格 " hello ".trimEnd()" hello"

7.2 大小写转换与忽略大小写比较

let str = "Hello World";
console.log(str.toUpperCase());    // "HELLO WORLD"
console.log(str.toLowerCase());    // "hello world"

function equalsIgnoreCase(a: string, b: string): boolean {
    return a.toLowerCase() === b.toLowerCase();
}
console.log(equalsIgnoreCase("Hello", "HELLO"));  // true

7.3 判断开头/结尾/包含(startsWith/endsWith/includes)

let filename = "document.pdf";
console.log(filename.endsWith(".pdf"));     // true
console.log(filename.startsWith("doc"));    // true
console.log(filename.includes("ume"));      // true

// 第二个参数:起始搜索位置
console.log("hello world".includes("o", 5)); // true

7.4 截取子串(slice/substring 区别)

方法 特点 示例
slice(start, end) 支持负数索引,end 不包含 "hello".slice(1,4)"ell"
substring(start, end) 负数当作 0,自动交换大小 "hello".substring(4,1)"ell"
let str = "Hello World";
console.log(str.slice(0, 5));    // "Hello"
console.log(str.slice(-5));      // "World"
console.log(str.substring(0, 5)); // "Hello"

7.5 分割与拼接(split/join)

// split - 字符串 → 数组
let str = "apple,banana,orange";
let arr = str.split(",");           // ["apple", "banana", "orange"]
let limited = str.split(",", 2);    // ["apple", "banana"]

// join - 数组 → 字符串
let fruits = ["苹果", "香蕉", "橙子"];
console.log(fruits.join("-"));    // "苹果-香蕉-橙子"

7.6 替换操作(replace/replaceAll/正则)

let text = "hello world hello";

console.log(text.replace("hello", "hi"));      // "hi world hello"
console.log(text.replaceAll("hello", "hi"));   // "hi world hi"

// 正则全局替换(大小写不敏感)
console.log(text.replace(/hello/gi, "hi"));

7.7 字符串遍历(for/for…of/Array.from)

let str = "Hello";

// 方式1:for 循环
for (let i = 0; i < str.length; i++) {
    console.log(str[i]);
}

// 方式2:for...of(推荐,正确处理 Unicode)
for (let char of str) {
    console.log(char);
}

// 方式3:转为数组
let chars = Array.from(str);  // ["H", "e", "l", "l", "o"]

八、小数处理与类型互转详解(超详细)

8.1 小数取几位:toFixed() 的返回值陷阱与精度问题

toFixed(n) 将数字四舍五入为指定小数位数的字符串

let num = 3.1415926;

console.log(num.toFixed(2));   // "3.14"  (注意:是字符串!)
console.log(typeof num.toFixed(2));  // "string"

// 边界情况:浮点数精度导致四舍五入错误
let num2 = 1.005;
console.log(num2.toFixed(2));  // "1.00" ❌ 预期应该是 "1.01"

// 解决方案:使用更精确的算法
function preciseRound(num: number, decimals: number): number {
    const factor = Math.pow(10, decimals);
    return Math.round(num * factor) / factor;
}
console.log(preciseRound(1.005, 2));  // 1.01 ✅

// 如果需要得到数字类型
let fixedNum = Number(num.toFixed(2));  // 3.14(数字)

8.2 判断是否为有效数字:isNaN() / Number.isNaN() / isFinite() 详解

isNaN() vs Number.isNaN() 的区别

方法 是否会先转换类型 isNaN("abc") isNaN(NaN)
isNaN() ✅ 是(先转数字) true true
Number.isNaN() ❌ 否(不转换) false true
// isNaN() - 会先尝试将参数转换为数字
console.log(isNaN(NaN));         // true
console.log(isNaN("abc"));       // true("abc" 转数字是 NaN)
console.log(isNaN("123"));       // false("123" 转数字是 123)
console.log(isNaN(undefined));   // true

// Number.isNaN() - 不转换,只有真正的 NaN 才返回 true
console.log(Number.isNaN(NaN));     // true
console.log(Number.isNaN("abc"));   // false("abc" 不是 NaN 类型)
console.log(Number.isNaN(123));     // false

// isFinite() - 判断是否为有限数字
console.log(isFinite(123));      // true
console.log(isFinite(Infinity)); // false
console.log(isFinite(NaN));      // false
console.log(isFinite("123"));    // true(会先转换)

// Number.isFinite() - 不转换版本
console.log(Number.isFinite("123"));  // false(类型不是 number)

最佳实践:判断字符串是否可以转为有效数字

function isValidNumber(str: string): boolean {
    // 去除空格后,空字符串返回 false
    let trimmed = str.trim();
    if (trimmed === "") return false;
    
    // 使用 Number() 转换并检查是否为 NaN
    let num = Number(trimmed);
    return !isNaN(num) && isFinite(num);
}

console.log(isValidNumber("123"));     // true
console.log(isValidNumber("12.34"));   // true
console.log(isValidNumber("-5.6"));    // true
console.log(isValidNumber("123px"));   // false
console.log(isValidNumber(""));        // false
console.log(isValidNumber("  "));      // false
console.log(isValidNumber("abc"));     // false

8.3 字符串转数字:Number / parseInt / parseFloat / 一元加号 全面对比

方法 "123" "12.34" "123px" "abc" "" " "
Number() 123 12.34 NaN NaN 0 0
parseInt(str,10) 123 12 123 NaN NaN NaN
parseFloat() 123 12.34 123 NaN NaN NaN
+str 123 12.34 NaN NaN 0 0
// 详细示例
console.log(Number("123"));       // 123
console.log(Number("12.34"));     // 12.34
console.log(Number("123px"));     // NaN(❌ 注意:带单位会失败)
console.log(Number(""));          // 0(⚠️ 空字符串转为 0)
console.log(Number("  "));        // 0

console.log(parseInt("123px", 10));  // 123(✅ 忽略非数字后缀)
console.log(parseInt("12.34", 10));  // 12(只取整数部分)
console.log(parseInt("abc123", 10)); // NaN
console.log(parseInt("08", 10));     // 8(必须指定进制)

console.log(parseFloat("12.34.56")); // 12.34(遇到第二个点停止)
console.log(parseFloat("12.34abc")); // 12.34

// 一元加号
console.log(+"123");        // 123
console.log(+"12.34");      // 12.34
console.log(+"123px");      // NaN

8.4 数字转字符串:toString / String / 拼接

方法 示例 特点
.toString() (123).toString()"123" 可指定进制
String() String(123)"123" 处理 null/undefined 友好
拼接空字符串 123 + """123" 隐式转换
let num = 123.456;

console.log(num.toString());       // "123.456"
console.log(num.toFixed(2));       // "123.46"(同时做四舍五入)
console.log(String(num));          // "123.456"
console.log(num + "");             // "123.456"

// 进制转换
let n = 255;
console.log(n.toString(16));  // "ff"(十六进制)
console.log(n.toString(2));   // "11111111"(二进制)

8.5 浮点数精度问题与多种解决方案

8.5.1 问题演示(0.1 + 0.2 ≠ 0.3)

JavaScript/ArkTS 使用 IEEE 754 双精度浮点数,某些小数无法精确表示。

console.log(0.1 + 0.2);              // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);      // false

console.log(0.1 + 0.7);              // 0.7999999999999999
console.log(0.2 + 0.4);              // 0.6000000000000001
console.log(0.3 - 0.2);              // 0.09999999999999998
8.5.2 解决方案一:先乘整数再除(推荐)

原理:将小数乘以 10 的 n 次方转为整数运算,结果再除以同一个倍数。

// 通用函数:精确加法
function add(a: number, b: number, decimals: number = 10): number {
    const factor = Math.pow(10, decimals);
    return (Math.round(a * factor) + Math.round(b * factor)) / factor;
}

console.log(add(0.1, 0.2));   // 0.3 ✅
console.log(add(0.1, 0.7));   // 0.8 ✅

// 精确减法
function subtract(a: number, b: number, decimals: number = 10): number {
    const factor = Math.pow(10, decimals);
    return (Math.round(a * factor) - Math.round(b * factor)) / factor;
}

// 精确乘法
function multiply(a: number, b: number, decimals: number = 10): number {
    const factor = Math.pow(10, decimals);
    return (Math.round(a * factor) * Math.round(b * factor)) / (factor * factor);
}

// 精确除法
function divide(a: number, b: number, decimals: number = 10): number {
    if (b === 0) throw new Error("除数不能为0");
    const factor = Math.pow(10, decimals);
    return (Math.round(a * factor) / Math.round(b * factor));
}

// 使用示例
console.log(0.1 * 0.2);              // 0.020000000000000004
console.log(multiply(0.1, 0.2));     // 0.02 ✅
8.5.3 解决方案二:精度容差法(比较两个浮点数)

不追求完全相等,而是判断差值是否小于一个极小值(epsilon)。

// 比较两个浮点数是否相等
function isFloatEqual(a: number, b: number, epsilon: number = 1e-10): boolean {
    return Math.abs(a - b) < epsilon;
}

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

// 比较两个浮点数的大小关系
function isFloatGreater(a: number, b: number, epsilon: number = 1e-10): boolean {
    return a - b > epsilon;
}

function isFloatLess(a: number, b: number, epsilon: number = 1e-10): boolean {
    return b - a > epsilon;
}

console.log(isFloatGreater(0.1 + 0.2, 0.3));   // false(视为相等)
console.log(isFloatGreater(0.3, 0.1 + 0.2));   // false
8.5.4 解决方案三:使用 toFixed + Number(简单场景)
function safeAdd(a: number, b: number, decimals: number = 10): number {
    return Number((a + b).toFixed(decimals));
}

console.log(safeAdd(0.1, 0.2));   // 0.3 ✅
console.log(safeAdd(0.1, 0.7));   // 0.8 ✅

// 注意事项:toFixed 本身也有精度问题
console.log(1.005.toFixed(2));    // "1.00" ❌ 还是有问题
// 结合前面写过的 preciseRound 函数
function betterAdd(a: number, b: number, decimals: number = 10): number {
    return preciseRound(a + b, decimals);
}
8.5.5 解决方案四:使用第三方库(高精度场景)

如果需要极高精度(如金融计算),可考虑使用 decimal.jsbig.js(需要额外安装)。

// 伪代码示例(需安装 decimal.js)
// import Decimal from 'decimal.js';
// let a = new Decimal(0.1);
// let b = new Decimal(0.2);
// let result = a.plus(b).toNumber();  // 0.3

8.6 实战综合示例:表单价格输入处理

// 模拟用户输入的价格字符串,需要转为数字(保留2位小数)
function parseAndRoundPrice(input: string): number | null {
    // 1. 去除首尾空格
    let trimmed = input.trim();
    if (trimmed === "") return null;
    
    // 2. 转为数字
    let num = Number(trimmed);
    
    // 3. 判断是否为有效数字
    if (isNaN(num) || !isFinite(num)) {
        return null;
    }
    
    // 4. 使用精确四舍五入保留2位小数(先乘后除方式,避免 toFixed 精度问题)
    let rounded = Math.round(num * 100) / 100;
    
    return rounded;
}

// 测试
console.log(parseAndRoundPrice("12.345"));   // 12.35
console.log(parseAndRoundPrice("  -5.678 ")); // -5.68
console.log(parseAndRoundPrice("100px"));    // null
console.log(parseAndRoundPrice(""));         // null
console.log(parseAndRoundPrice("  "));       // null

// 金额累加(防止浮点数精度问题)
function sumPrices(prices: number[]): number {
    // 先乘100转为整数(分),累加后再除以100
    let totalCents = prices.reduce((sum, price) => {
        return sum + Math.round(price * 100);
    }, 0);
    return totalCents / 100;
}

let prices = [0.1, 0.2, 0.3];
console.log(sumPrices(prices));  // 0.6 ✅(而不是 0.6000000000000001)

九、总结速查表

分支判断速查

场景 正确写法 错误写法
单选(等级、权限) if-else if-else 多个独立 if
多选(同时校验多个条件) 多个独立 if if-else if

比较运算符速查

符号 名称 推荐度
= 赋值 仅用于赋值
== 松散相等 ❌ 不推荐
=== 严格相等 ✅ 推荐

假值列表(Falsy)

false0-00n""nullundefinedNaN

Math 常用方法速查

方法 作用 示例结果
Math.floor(3.9) 向下取整 3
Math.ceil(3.1) 向上取整 4
Math.round(3.5) 四舍五入 4
Math.random() 随机数 [0,1) 0~0.999
Math.max(1,5,3) 最大值 5
Math.abs(-5) 绝对值 5
Math.pow(2,3) 幂运算 8

Date 常用方法速查

方法 返回值范围
getFullYear() 2026
getMonth() 0~11(需+1)
getDate() 1~31
getDay() 0~6(0=周日)
getTime() 时间戳(毫秒)

判断数字有效性速查

场景 推荐方法
判断一个值是否为真正的 NaN Number.isNaN(value)
判断字符串是否可转为有效数字 !isNaN(Number(str)) && str.trim() !== ""
判断一个数字是否为有限数 isFinite(num)

字符串转数字速查

输入 Number() parseInt() parseFloat()
"123" 123 123 123
"12.34" 12.34 12 12.34
"123px" NaN 123 123
"abc" NaN NaN NaN
"" 0 NaN NaN

浮点数精度问题解决方案

方案 适用场景 示例
先乘整数再除 加减乘除通用 Math.round(a*100 + b*100)/100
精度容差法 比较两个浮点数 Math.abs(a-b) < 1e-10
toFixed + Number 简单场景(注意精度) Number((a+b).toFixed(10))

希望这份超详细的指南能帮你彻底理解鸿蒙ArkTS中的这些基础知识点。如果还有任何疑问,欢迎在评论区留言交流!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐