表单里的年龄通常以文本 "19" 进入程序,购物车数量却要按数字参与计算;用户没填昵称时,程序还要分清“暂时没有值”和“明确留空”。这些看似细小的差别,会直接决定程序得到 38、1919,还是一个无法继续计算的 NaN。
要把结果算对,先别急着记运算符。更重要的是建立三个清楚的概念:值是什么、值属于什么类型、变量名当前绑定到哪个值。
这一章会从真实的数据录入场景出发,逐步认识变量声明、七种原始类型、类型识别与转换,再用对象引用解释一个常见现象:为什么改了 alias.score,user.score 也会跟着改变。
完成本章后,你应该能够:
const 与 let 之间做选择;BigInt;typeof、String()、Number() 与 Boolean() 检查或转换数据;undefined、null、假值与 NaN;本章所有控制台示例都可以在现代浏览器 Console 或 Node.js 中运行。文中列出的输出使用 Node.js v25.2.1 验证;这里只使用 JavaScript 语言本身提供的能力,不依赖浏览器页面 API。
假设一个报名页面收到年龄 18。程序真正处理的是数字值 18;number 是这个值的类型;age 则是我们为它取的变量名。
let age = 18;
age = 19;
console.log(age);
console.log(typeof age);运行环境:Node.js v25.2.1
19
number第一行声明了变量名 age,让它绑定到数字 18。第二行没有“改造”数字 18,而是让 age 改为绑定到另一个数字 19。

变量名像可读的标签;值才是程序计算、比较和传递的对象。
JavaScript 的值大致可以分为两类:
原始值不可变,不等于绑定它的变量不能换绑。下面的 word 可以换成新字符串,但字符串 "cat" 本身没有被原地修改:
let word = "cat";
word = word.toUpperCase();
console.log(word);CATJavaScript 还是一门动态类型语言:类型属于值,不固定属于变量名。下面的代码合法,但实际项目里频繁让同一个名字更换类型会降低可读性。
let status = "loading";
status = true;
console.log(typeof status);boolean现代 JavaScript 最常用的声明方式是 const 和 let:
const,表示这个名字不会重新绑定;let;var,因为它的作用域与提升行为更容易带来误解。计数器、当前页码、加载状态等会变化的绑定适合用 let。
let score = 80;
score = 85;
console.log(score);85const 声明时必须给初始值,而且变量名不能再指向另一个值。若它绑定的是对象,对象内部属性仍然可以改变。
const user = { name: "小林", score: 80 };
user.score = 85;
console.log(user.score);
try {
user = { name: "小林", score: 90 };
} catch (error) {
console.log(error.name);
}运行环境:Node.js v25.2.1
85
TypeError
let 与 const 以代码块为边界;var 不把普通代码块当作作用域。
下面的 legacyName 能越过 if 的花括号,但仍然不会越过 inspectScope() 这个函数:
function inspectScope() {
if (true) {
const blockName = "只在块内";
var legacyName = "仍在函数内";
}
console.log(legacyName);
try {
console.log(blockName);
} catch (error) {
console.log(error.name);
}
}
inspectScope();仍在函数内
ReferenceErrorJavaScript 标识符区分大小写,userName 与 username 是两个名字。实用的命名规则包括:
camelCase:orderTotal、ageOfBuyer;_、$,但不能以数字开头;let、class、return 等保留字;is、has、can 等前缀,如 isLoggedIn;x、data1 这类无法说明用途的名字,除非上下文非常短且含义明确。一个稳定的选择顺序是:先问“这个名字之后需要重新赋值吗?”不需要就用 const,需要才用 let。这比先写 let、以后再猜它会不会变化更清楚。
字符串是字符序列,用来表示姓名、提示语、商品编号等文本。单引号、双引号和反引号都能创建字符串:
const singleQuoted = '你好';
const doubleQuoted = "JavaScript";
const course = "变量与类型";
const message = `正在学习:${course}`;
console.log(singleQuoted);
console.log(doubleQuoted);
console.log(message);你好
JavaScript
正在学习:变量与类型单引号和双引号在能力上没有区别,团队统一一种风格即可。反引号创建的是模板字符串;${表达式} 会先求值,再把结果放进字符串。
反斜杠 \ 会改变下一个字符的含义。常用转义包括:
\":字符串中的双引号;\':字符串中的单引号;\\:一个真正的反斜杠;\n:换行;\t:制表符。const quote = "她说:\"继续试一次。\"";
const path = "C:\\projects\\demo";
const lines = "第一行\n第二行";
console.log(quote);
console.log(path);
console.log(lines);她说:"继续试一次。"
C:\projects\demo
第一行
第二行模板字符串还可以直接跨行,不过缩进和换行也会成为字符串内容。生成对空白敏感的文本时,要检查最终结果。
字符串是原始值,不能把其中某个字符原地改掉。字符串方法会返回新字符串:
const original = " JavaScript ";
const cleaned = original.trim().toUpperCase();
console.log(JSON.stringify(original));
console.log(cleaned);" JavaScript "
JAVASCRIPTtrim() 去掉两端空白,toUpperCase() 转成大写。original 仍保留原来的内容。
JavaScript 的 number 同时表示整数和小数,也支持科学计数法以及二进制、八进制、十六进制字面量。
const integer = 42;
const decimal = 3.14;
const exponential = 1.2e3;
const binary = 0b1010;
const octal = 0o10;
const hexadecimal = 0x0f;
console.log(integer, decimal, exponential);
console.log(binary, octal, hexadecimal);
console.log(typeof binary);42 3.14 1200
10 8 15
number不同写法只影响源码如何表达数值,不会产生新的运行时类型。0b1010 最终仍是 number 类型的十进制数值 10。
number 采用双精度浮点数表示。它能表达非常大的数量级,但并非范围内的每一个整数都能保持精确。超过 Number.MAX_SAFE_INTEGER 后,相邻整数可能映射到同一个结果。
const maxSafe = Number.MAX_SAFE_INTEGER;
console.log(maxSafe);
console.log(maxSafe + 1);
console.log(maxSafe + 2);
console.log(maxSafe + 1 === maxSafe + 2);9007199254740991
9007199254740992
9007199254740992
true金额计算也要留意浮点误差。对货币这类需要精确小数的场景,常见做法是用最小单位整数计算,例如把 19.99 元表示为 1999 分,再按业务要求格式化。
在整型编号或超大整数运算中,可以在整数字面量后加 n 创建 bigint:
const huge = 9007199254740993n;
const next = huge + 1n;
console.log(huge);
console.log(next);
console.log(typeof huge);9007199254740993n
9007199254740994n
bigintbigint 不能直接与 number 混合运算;转换前必须先确认精度和业务含义。
try {
console.log(10n + 1);
} catch (error) {
console.log(error.name);
}TypeError
七种原始类型解决不同问题;两个“空值”也表达不同意图。
布尔值只有 true 和 false,适合表示开关、权限和判断结果。它们不是字符串,大小写也不能改变。
const isLoggedIn = true;
const hasPermission = false;
console.log(isLoggedIn, typeof isLoggedIn);
console.log(hasPermission, typeof hasPermission);true boolean
false boolean"false" 是包含五个字符的字符串,不是布尔值 false。这一区别在表单数据转换时尤其重要。
Symbol() 常用于需要避免名称冲突的唯一标记。描述文字只帮助调试,不参与相等性:
const firstId = Symbol("id");
const secondId = Symbol("id");
console.log(firstId === secondId);
console.log(String(firstId));false
Symbol(id)即使描述都是 "id",两次调用 Symbol() 也会产生两个不同的值。
变量声明后还没有初始值、对象缺少某个属性、函数没有明确返回结果时,都常见 undefined。null 通常由程序员主动写下,表达“这里目前明确没有对象或结果”。
let nickname;
const selectedUser = null;
const profile = {};
console.log(nickname);
console.log(profile.city);
console.log(selectedUser);
console.log(nickname === undefined);
console.log(selectedUser === null);undefined
undefined
null
true
true业务数据若要明确保存“已检查但没有结果”,null 往往比手动赋值 undefined 更能说明意图。接口规范若有自己的约定,则应保持统一。
typeof 是操作符,不是函数。typeof value 与 typeof(value) 都能运行,通常直接使用前一种写法。
const samples = [
["小林", "小林"],
["42", 42],
["9007199254740993n", 9007199254740993n],
["true", true],
['Symbol("id")', Symbol("id")],
["undefined", undefined],
["null", null],
运行环境:Node.js v25.2.1
小林: string
42: number
9007199254740993n: bigint
true: boolean
Symbol("id"): symbol
undefined: undefined
null: object
{ score: 80 }: object这里有两个必须单独处理的边界:
typeof null 返回 "object",这是长期保留的历史行为;判断 null 应写 value === null。typeof 只给出较粗的类别。数组和普通对象都会得到 "object";需要识别数组时可使用 Array.isArray(value)。函数还有一个独特结果:typeof function () {} 会返回 "function"。它便于判断一个值能否按函数调用,但 function 并不是第八种原始类型。
页面输入框提交的值通常是字符串。若数量 "2" 要参与数学计算,应先明确转换,而不是依赖运算时的自动转换。

先把数据整理成预期类型,再开始业务计算,结果更容易预测。
console.log("2" + 2);
console.log("2" * 2);22
4+ 同时承担数字加法和字符串连接。只要它走到字符串连接分支,数字 2 就会转成文本;乘法没有“字符串乘法”的含义,因此 "2" 会尝试转成数字。
这类自动转换并非随机,但把每条规则都藏在表达式里会增加阅读成本。输入边界处先显式转换,通常更清楚:
const countText = "2";
const total = Number(countText) + 2;
console.log(total);4console.log(String(42), typeof String(42));
console.log(String(null));
console.log(String(undefined));42 string
null
undefined控制台不显示字符串两边的引号,所以同时查看 typeof 更容易确认转换结果。
const converted = [
Number("12"),
Number(""),
Number(" "),
Number(null),
Number(undefined),
Number("hello"),
];
console.log(converted);
console.log(typeof NaN);
console.log(Number.[ 12, 0, 0, 0, NaN, NaN ]
number
trueNaN 的含义是“这次数字转换或计算没有得到有效数字”,但它本身属于 number 类型。检查它应使用 Number.isNaN(value)。
如果空字符串在业务上表示“用户没有填写”,不要因为 Number("") 恰好得到 0 就把它当成真实的零。应先检查原始文本是否为空,再决定使用 null、提示错误,还是应用默认值。
console.log(Boolean("false"));
console.log(Boolean(""));
console.log(Boolean(0));
console.log(Boolean([]));true
false
false
trueBoolean("false") 得到 true,因为 JavaScript 只看到一个非空字符串,不会理解里面的英文含义。若接口使用 "true" 与 "false" 传递状态,应按明确规则解析:
const rawEnabled = "false";
const isEnabled = rawEnabled === "true";
console.log(isEnabled);falseif、while 和三元表达式的条件最终需要布尔意义。条件不是布尔值时,JavaScript 会按 Boolean(value) 的规则判断。
常见假值一共包括:
false0、-00n""nullundefinedNaN除此之外,大多数值都是真值。尤其容易误判的是非空字符串 "0"、字符串 "false"、空数组 [] 和空对象 {}——它们都是真值。
const values = [false, 0, 0n, "", null, undefined, NaN, "0", [], {}];
for (const value of values) {
console.log(typeof value, Boolean(value));
}前七项会得到 false,最后三项会得到 true。数组和对象打印时外观可能因控制台而异,所以这里直接观察类型和布尔结果。
不要把“是假值”与“等于布尔值 false”混为一谈。前者描述值放进条件时的行为;后者属于相等比较,会引入另一套转换规则。要观察条件行为,直接写 Boolean(value) 最清楚。
原始值赋给另一个变量时,可以把两边理解为各自绑定到那个原始值。对象赋值则不同:两个变量名可能指向同一个对象。
let firstScore = 80;
let copiedScore = firstScore;
copiedScore = 90;
console.log(firstScore, copiedScore);
const user = { name: "小林", score: 80 };
const alias = user;
alias.score = 90;
console.log(user.score, alias.score);
console.log(user === alias);80 90
90 90
truecopiedScore 换绑到数字 90 不会影响 firstScore。user 与 alias 却共享同一个对象,所以通过任一名字修改 score,另一边都能看到变化。
{ ...user } 会创建新的外层对象。只有原始值属性时,这通常足以隔离修改:
const user = { name: "小林", score: 80 };
const copied = { ...user };
copied.score = 95;
console.log(user.score, copied.score);
console.log(user === copied);80 95
false但展开语法是浅拷贝。若属性值本身还是对象,内层引用仍会共享:
const user = {
name: "小林",
settings: { theme: "light" },
};
const copied = { ...user };
copied.settings.theme = "dark";
console.log(user.settings.theme);
console.log(user.settings === copied.settings);dark
true因此,看到 const 不能推断对象不可变,看到展开语法也不能推断所有层级都已完全独立。先看变量绑定到了什么,再看对象内部是否还含有共享引用。
先在类型转换观察器中输入不同值,比较 typeof、Boolean()、Number() 与 String() 的结果。遇到意外结果时,先写下预测,再点击运行。
接着在绑定实验台中逐步执行“复制原始值”“共享对象”“浅拷贝对象”,观察每个变量名指向哪里。
完成实验后,尝试用一句话分别解释:
Boolean("false") 是 true;Number("") 是 0,却不一定适合当作业务数据;页面表单把所有输入先交给程序,当前拿到的是下面这组原始数据:
const rawProfile = {
name: " 小林 ",
age: "19",
newsletter: "false",
inviter: "",
};请创建一个新的 profile 对象,完成这些整理规则:
trim() 去掉姓名两端空白;number,并用 Number.isNaN() 检查转换是否失败;"true" 时,newsletter 才是布尔值 true;inviter 设为 null;previewProfile 浅拷贝,把它的姓名改为 "预览用户",确认不会改动 profile.name。可以从这个骨架开始:
const rawProfile = {
name: " 小林 ",
age: "19",
newsletter: "false",
inviter: "",
};
// TODO:把 rawProfile.age 转成数字
const age = rawProfile.age;
// TODO:在条件中检查 age 是否无效
if (false) {
throw new Error("年龄必须是数字");
}
const profile = {
程序应稳定输出:
小林|19|false|null
string number boolean
true
小林 预览用户另外再把 age 改成 "十九" 测试一次,程序应抛出“年龄必须是数字”,而不是让无效的 NaN 静默进入 profile。
const,确实需要重新赋值时用 let,新代码尽量避免 var。string、number、bigint、boolean、symbol、undefined、null。${表达式} 插值,反斜杠用于引号、换行和路径等转义。number 存在精度边界;超大整数可用 bigint,但不能与 number 直接混算。typeof null 是 "object";判断明确空值应写 value === null。String()、Number() 和明确的布尔解析,通常比依赖自动转换更可靠。这份实现把转换放在数据进入业务对象之前。这样 profile 一旦创建,其中每个字段的类型就已经符合约定,后面的代码不必重复猜测。