JavaScript学习笔记:7.数字和字符串

JavaScript学习笔记:7.数字和字符串

上一篇吃透了表达式和运算符的“连接技巧”,这一篇咱们聚焦JS世界最基础、也最容易踩坑的两个“老伙计”——数字(Number)和字符串(String)。如果把JS的数据类型比作生活工具,那数字就是“记账本”(负责计算、统计),字符串就是“日记本”(负责记录、展示),几乎所有业务逻辑都绕不开它们。

新手常栽在“0.1+0.2≠0.3”的迷思里,被字符串“不可变性”搞得一头雾水,甚至不知道"10"10的本质区别。今天就用“生活化场景+踩坑实录”的方式,把这俩基础类型的核心知识点、避坑技巧讲透,让你既能“算对账”,又能“记对事”。

一、数字:JS的“记账本”,藏着精度陷阱

数字是JS里处理数值的核心类型,但它不像现实中的“整数/小数”那么简单——JS只有一种基本数字类型Number(对应64位浮点数),还有专门处理大整数的BigInt,这俩合起来才构成完整的“数字体系”。

1. 先搞懂:Number的“两面性”

Number类型看着能存整数、小数,实则本质是“双精度浮点数”,这就导致它有个致命陷阱:无法精确表示所有小数,就像现实中“1/3=0.333…”永远写不完一样。

经典坑:0.1+0.2≠0.3

这是面试必问的“开胃菜”,也是新手最容易懵的场景:

console.log(0.1 + 0.2); // 0.30000000000000004(不是0.3!)
console.log(0.1 + 0.2 === 0.3); // false(坑哭无数人)

原因:计算机用二进制存储小数,0.1和0.2转成二进制是无限循环小数,JS只能保留前53位有效数字,导致精度丢失。

避坑方案:用“整数运算”替代“小数运算”

现实中处理金额时,别直接用小数计算,先转成“分”(扩大100倍)做整数运算,最后再转回来:

// 反面例子:小数直接运算
const price1 = 0.1; // 1角
const price2 = 0.2; // 2角
console.log((price1 + price2).toFixed(1)); // 0.3(表面对,但隐患大)

// 正面例子:转整数运算(推荐)
const price1Cent = 10; // 10分=1角
const price2Cent = 20; // 20分=2角
const totalCent = price1Cent + price2Cent;
const totalPrice = totalCent / 100; // 0.3(精确)

2. Number的“叛逆少年”:NaN

NaN(Not a Number)是数字类型里的“异类”——它表示“不是有效数字”,但typeof检测结果是number,还自带“六亲不认”的特性:

console.log(typeof NaN); // "number"(离谱但事实)
console.log(NaN === NaN); // false(连自己都不认)
console.log(NaN + 10); // NaN(和任何数运算都是NaN)
实用技巧:判断NaN的正确姿势

别用===判断,要用专门的Number.isNaN()(ES6+),它只认“真正的NaN”:

// 反面例子:isNaN()会误判
console.log(isNaN("abc")); // true("abc"是字符串,不是NaN)
console.log(isNaN(NaN)); // true

// 正面例子:Number.isNaN()精准判断
console.log(Number.isNaN(NaN)); // true(正确)
console.log(Number.isNaN("abc")); // false(正确)

3. 大整数的“救星”:BigInt

Number类型有个上限:2^53 - 1(约9007亿),超过这个数就会丢失精度,比如后端返回的“订单号”“用户ID”常是大整数,直接用Number存就会出错:

console.log(9007199254740992); // 9007199254740992
console.log(9007199254740993); // 9007199254740992(精度丢失!)
BigInt的用法:结尾加n或用BigInt()转换
// 1. 直接加n定义BigInt
const bigNum1 = 9007199254740993n;
console.log(bigNum1); // 9007199254740993n(精确)

// 2. 用BigInt()转换(注意:不能转小数,会报错)
const bigNum2 = BigInt("9007199254740993");
console.log(bigNum2); // 9007199254740993n

// 3. 运算:BigInt和Number不能混用,要统一类型
console.log(bigNum1 + BigInt(10)); // 9007199254741003n

4. 数字的实用方法:别再自己写工具函数

JS给数字内置了很多方法,不用自己造轮子,重点记这几个高频的:

  • toFixed(n):保留n位小数(返回字符串,注意四舍五入的坑)
  • Math.round():四舍五入到整数
  • Math.floor()/Math.ceil():向下/向上取整
  • Number.isInteger():判断是否是整数
// toFixed的坑:四舍五入不是“数学四舍五入”,是“银行家舍入法”
console.log((1.35).toFixed(1)); // 1.4(对)
console.log((1.25).toFixed(1)); // 1.2(坑!因为二进制精度问题)

// 解决:用Math.round配合乘法
function roundNum(num, n) {
  const base = Math.pow(10, n);
  return Math.round(num * base) / base;
}
console.log(roundNum(1.25, 1)); // 1.3(正确)

// 判断是否是整数(别用num % 1 === 0,会误判NaN)
console.log(Number.isInteger(10)); // true
console.log(Number.isInteger(10.0)); // true
console.log(Number.isInteger(10.5)); // false

二、字符串:JS的“日记本”,不可变是核心

字符串是用来存储文本的类型,就像现实中的“日记本”——写上去的内容不能直接改(不可变性),只能重新写一本。这个“不可变性”是字符串的核心,也是新手最容易踩的坑。

1. 核心特性:字符串是“不可变的”

很多新手以为能直接修改字符串的某个字符,结果发现没效果,就是因为字符串的不可变性:

// 反面例子:试图修改字符串,无效
let str = "奶茶";
str[0] = "咖"; // 看似修改,实则无效
console.log(str); // "奶茶"(还是原来的)

// 正面例子:想改字符串,只能重新生成新字符串
str = "咖" + str[1]; // 拼接新字符串
console.log(str); // "咖啡"(正确)

原因:字符串在内存中是“只读”的,修改操作本质是生成新字符串,原字符串不会变。这就像日记本上的字改不了,只能撕下来重写一页。

2. 字符串的“外衣”:单引号、双引号、反引号

JS里字符串有三种包裹方式,各有特点,别混用:

  • 单引号/双引号:功能一样,选一种统一用(避免嵌套时转义)
  • 反引号(`):ES6模板字面量,支持多行字符串和变量插值(开发首选)
// 1. 单/双引号嵌套:需要用反斜杠转义
const str1 = '他说:"我喜欢喝奶茶"'; // 双引号嵌套在单引号里
const str2 = "他说:\"我喜欢喝奶茶\""; // 双引号嵌套,需要转义

// 2. 反引号的优势1:多行字符串(不用加\n)
const str3 = `我喜欢喝:
- 珍珠奶茶
- 生椰拿铁`;
console.log(str3); // 保留换行,格式清晰

// 3. 反引号的优势2:变量插值(不用加+拼接)
const drink = "奶茶";
const str4 = `我今天喝了3杯${drink}`; // 直接嵌入变量
console.log(str4); // "我今天喝了3杯奶茶"

// 4. 插值里还能写表达式
const a = 10;
const b = 20;
const str5 = `10+20=${a + b}`; // "10+20=30"

3. 字符串的高频方法:处理文本不用愁

字符串的方法很多,但高频实用的就这几个,记牢能省大量时间:

(1)查找类:判断是否包含某个内容
  • includes(substr):返回布尔值,判断是否包含(ES6+,推荐)
  • indexOf(substr):返回首次出现的索引,没找到返回-1(兼容旧浏览器)
  • startsWith(substr)/endsWith(substr):判断是否以某字符开头/结尾
const str = "奶茶加珍珠,珍珠要多放";
console.log(str.includes("珍珠")); // true(是否包含)
console.log(str.indexOf("珍珠")); // 3(首次出现的位置)
console.log(str.indexOf("椰果")); // -1(没找到)
console.log(str.startsWith("奶茶")); // true(以奶茶开头)
console.log(str.endsWith("放")); // true(以放结尾)
(2)截取类:提取部分字符串
  • slice(start, end):从start截到end(不包含end),支持负数(从末尾算)
  • substring(start, end):和slice类似,但不支持负数(不推荐)
  • substr(start, length):从start开始截length个字符(逐渐废弃,别用)
const str = "abcdefgh";
// slice:推荐,支持负数
console.log(str.slice(2, 5)); // "cde"(从2截到5,不包含5)
console.log(str.slice(-3)); // "fgh"(从末尾第3个开始截到最后)

// substring:不支持负数,会自动交换参数(容易懵)
console.log(str.substring(5, 2)); // "cde"(自动交换成2,5)
(3)修改类:生成新字符串(因为不可变)
  • trim()/trimStart()/trimEnd():去除空格(前后/前/后)
  • replace(oldStr, newStr):替换指定内容(只替换首次,全局替换用正则)
  • toUpperCase()/toLowerCase():转大写/小写
// 去除空格(表单输入常用)
const input = "  用户名  ";
console.log(input.trim()); // "用户名"(去除前后空格)

// 替换内容
const str = "奶茶加珍珠,珍珠要多放";
console.log(str.replace("珍珠", "椰果")); // "奶茶加椰果,珍珠要多放"(只替换首次)
console.log(str.replace(/珍珠/g, "椰果")); // "奶茶加椰果,椰果要多放"(全局替换,用/g正则)

// 大小写转换
console.log("abc".toUpperCase()); // "ABC"
console.log("ABC".toLowerCase()); // "abc"

4. 字符串的坑:length不是“字符数”?

字符串的length属性看似是“字符个数”,但对Unicode字符(比如 emoji、中文生僻字)会“算错”,因为这些字符占2个字节:

console.log("奶茶".length); // 2(正常,中文占1个长度)
console.log("🥤".length); // 2(坑!emoji占2个长度)
console.log("𠮷".length); // 2(坑!生僻字占2个长度)

原因:JS的length是按“UTF-16编码单元”计算的,而不是“字符数”。解决这个问题需要用Array.from()转数组后再算长度:

function getRealLength(str) {
  return Array.from(str).length;
}
console.log(getRealLength("🥤")); // 1(正确)
console.log(getRealLength("奶茶🥤")); // 3(正确)

三、数字与字符串的转换:双向奔赴的技巧

数字和字符串的转换是高频场景,比如“表单输入是字符串,计算要转数字”“接口返回数字,展示要转字符串”。核心原则:主动转换比自动转换靠谱

1. 数字转字符串:三种方式

  • 方式1:String(num)(推荐,兼容性好)
  • 方式2:num.toString()(不能转NaN和Infinity,会报错)
  • 方式3:num + ""(简洁,但可读性差,不推荐)
const num = 123;
console.log(String(num)); // "123"(推荐)
console.log(num.toString()); // "123"
console.log(num + ""); // "123"(不推荐)

// 注意:toString()不能转NaN
const nanNum = NaN;
console.log(String(nanNum)); // "NaN"(正常)
console.log(nanNum.toString()); // "NaN"(居然能转?特殊情况,稳妥还是用String())

2. 字符串转数字:四种方式,按需选

  • 方式1:Number(str)(推荐,转整个字符串,无效则返回NaN)
  • 方式2:parseInt(str, 进制)(转整数,必须加进制,否则易坑)
  • 方式3:parseFloat(str)(转浮点数,只认十进制)
  • 方式4:+str(简洁,和Number()效果一样,适合简单场景)
const str1 = "123";
const str2 = "123.45";
const str3 = "123abc";

// Number():转整个字符串,有非数字就返回NaN
console.log(Number(str1)); // 123
console.log(Number(str2)); // 123.45
console.log(Number(str3)); // NaN(正确,因为有abc)

// parseInt():转整数,加进制!加进制!加进制!
console.log(parseInt(str1, 10)); // 123(十进制,推荐)
console.log(parseInt(str2, 10)); // 123(只取整数部分)
console.log(parseInt("010", 10)); // 10(十进制,正确)
console.log(parseInt("010", 8)); // 8(八进制,所以要加进制)

// +str:简洁,适合简单场景
console.log(+str1); // 123
console.log(+str2); // 123.45

3. 自动转换的坑:别靠JS“猜心思”

JS会在特定场景自动转换数字和字符串,比如用+ -运算符时,但这种“自动”往往是坑:

// 1. +:有字符串就拼接,没字符串就加
console.log("10" + 20); // "1020"(字符串拼接,坑)
console.log(10 + 20); // 30(数字加法)

// 2. - * /:会把字符串转数字
console.log("10" - 5); // 5(转数字减法)
console.log("10" * 2); // 20(转数字乘法)
console.log("10" / 2); // 5(转数字除法)

// 避坑:主动转类型,别靠自动转换
const a = "10";
const b = 20;
console.log(Number(a) + b); // 30(正确)

四、实战避坑总结:数字和字符串的“生存法则”

  1. 数字避坑三原则

    • 处理小数用“整数运算”:金额、精度要求高的场景,先扩大倍数转整数。
    • 大整数用BigInt:订单号、用户ID超过2^53-1,必须用BigInt。
    • 判断NaN用Number.isNaN():别用===,也别用旧的isNaN()
  2. 字符串避坑三原则

    • 记住“不可变性”:修改字符串本质是生成新字符串,别直接改索引。
    • 优先用反引号:多行字符串、变量插值直接搞定,不用拼接和转义。
    • 算字符数用Array.from():处理emoji、生僻字时,别信length属性。
  3. 转换避坑核心

    • 主动转换:用Number()String()手动转,拒绝依赖JS自动转换。
    • 表单输入必转数字:用户输入的内容都是字符串,计算前一定要转数字。

五、结尾:基础扎实,才能走得远

数字和字符串是JS的“基石类型”,看似简单,却藏着很多“反直觉”的坑——这些坑不是JS设计得差,而是我们没吃透它的底层逻辑(比如浮点数的二进制存储、字符串的不可变性)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿蒙Armon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值