A02 变量与数据类型
讲解 JavaScript 变量声明、七种原始类型与引用类型的判别方法及复制语义差异。
- const 约束引用重新绑定而非内容修改,新代码只用 const 与 let
- typeof 对数组与 null 返回 object,需用 Array.isArray 与 === null 判别
- 对象变量共享堆地址,赋值与浅拷贝只断开第一层引用
- Symbol 用于唯一属性名,BigInt 用于超 2^53 整数,均有限制
- 深层拷贝用 structuredClone,真正不可变需 Object.freeze
A02 变量与数据类型
摘要:变量是值的引用,
let与const决定引用能否重新绑定,而不是值能否修改。JavaScript 有七种原始类型与一种引用类型,typeof能判别多数原始类型但对数组与null会给出误导性结果。本文给出声明规则、类型判别表与复制语义差异,避免共享引用与隐式类型错误。
关键词:变量,原始类型,引用类型,typeof,不可变
所属:frontend/js / A-入门 | 上一篇:A01-认识JavaScript与运行环境 | 下一篇:A03-运算符与类型转换
1 引言
「改了一个对象,另一个变量也跟着变了」是入门阶段最常见的困惑,根因是把变量当成容器而不是引用。类型判断错误同样常见:typeof [] 返回 "object",typeof null 也返回 "object"。
本文说明三件事:怎么声明、值属于哪一类、复制时发生了什么。范围见表 1-1。
| 项 | 本文覆盖 | 本文不覆盖 |
|---|---|---|
| 声明 | let、const、var 的差异 |
作用域与闭包(B01) |
| 类型 | 七种原始类型、对象类型 | 类型转换规则(A03) |
| 复制 | 值复制与引用复制、浅拷贝 | 深拷贝与结构化克隆(C06) |
2 预备知识
| 项 | 要求 | 核对方式 |
|---|---|---|
| 环境 | 浏览器控制台可用 | 能执行表达式并看到结果 |
| 前置 | A01 | 能在页面中运行脚本 |
| 文件 | js-practice/types.js |
编码 UTF-8 |
3 原理与实现
3.1 变量指向值
原始类型的值直接存放在变量里,对象类型的变量只存放地址,如图 3-1 所示。
flowchart LR
A["let n = 1"] --> B["栈:n → 1"]
C["let o = {a:1}"] --> D["栈:o → 地址 0x1F"]
D --> E["堆:{a:1}"]
F["let p = o"] --> D
图 3-1 原始类型直接存值,对象类型共享同一地址
图上可见:
o与p指向同一个堆对象,任一方改属性另一方都可见;n的值变化不影响其它变量。
3.2 声明与复制
// js-practice/types.js;运行环境:Chrome 130+ 控制台或 Node.js 20+
const user = { name: 'Lin', tags: ['css', 'js'] };
let score = 90;
// const 约束的是绑定,不是内容
user.name = 'Chen'; // 允许
// user = {}; // 报错:Assignment to constant variable
// 引用复制
const alias = user;
alias.tags.push('html');
console.log(user.tags); // ['css', 'js', 'html']
// 浅拷贝断开第一层
const copy = { ...user, tags: [...user.tags] };
copy.tags.push('node');
console.log(user.tags.length, copy.tags.length); // 3 4
// 类型判别
const rows = [
['undefined', undefined],
['null', null],
['number', 1.5],
['bigint', 9007199254740993n],
['string', 'js'],
['boolean', true],
['symbol', Symbol('id')],
['object', { a: 1 }],
['array', []],
['function', () => {}],
];
for (const [label, value] of rows) {
console.log(label.padEnd(10), typeof value);
}
类型判别的可靠写法见表 3-1。
| 目标 | 推荐写法 | 说明 |
|---|---|---|
| 数组 | Array.isArray(v) |
typeof [] 返回 "object" |
null |
v === null |
typeof null 返回 "object"(历史遗留) |
| 普通对象 | v !== null && typeof v === 'object' |
排除 null 后判类型 |
| 整数 | Number.isInteger(v) |
不用 v % 1 === 0,后者对 Infinity 误判 |
| 函数 | typeof v === 'function' |
函数是唯一可调用的非原始值 |
var 与 let 的差异只记两点:var 提升到函数顶部且允许重复声明,let 提升但存在暂时性死区。新代码一律用 const,需要重新赋值时改 let,不用 var。
3.3 Symbol 与 BigInt 的用途
这两类值不参与常规运算,各自解决一个具体问题,写法如下。
// js-practice/types.js(续);运行环境:Node.js 20+
// 1 Symbol:唯一键,避免多个模块的属性名撞车
const KEY_ID = Symbol('id');
const KEY_NAME = Symbol('id'); // 描述相同也不相等
const record = { [KEY_ID]: 7, [KEY_NAME]: 'Lin' };
console.log(record[KEY_ID], record[KEY_NAME]); // 7 Lin
console.log(Object.keys(record)); // [],Symbol 键不出现在常规遍历中
console.log(Object.getOwnPropertySymbols(record).length); // 2
// 2 Symbol 作为协议入口:内置 Symbol 改变对象行为
const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
let n = this.from;
return { next: () => (n <= this.to ? { value: n++, done: false } : { done: true }) };
},
};
console.log([...range]); // [1, 2, 3]
// 3 BigInt:超过 2^53 的整数,后缀 n 或构造
const big = 9007199254740993n;
console.log(big + 1n); // 9007199254740994n
console.log(Number(big)); // 9007199254740992,精度丢失
// console.log(big + 1); // TypeError,不能与普通数字混算
两类值的取舍见表 3-3。
| 类型 | 解决的问题 | 限制 |
|---|---|---|
Symbol |
属性名唯一,避免多处定义冲突 | 不参与 for...in、JSON.stringify |
BigInt |
表示超过 2^53 的整数 ID |
不能与 number 混算,无 Math 方法 |
表 3-3 Symbol 与 BigInt 的用途与限制
4 验证与讨论
- 执行上述代码,确认
user.tags长度为 3,说明引用复制生效。 - 把
copy的展开写法改为const copy = { ...user },再执行copy.tags.push('x'),确认user.tags也被修改,说明浅拷贝只断开一层。 - 在控制台依次执行
typeof []、typeof null、Array.isArray([]),对照表 3-1。 - 在严格模式下给
const变量赋值,确认抛出TypeError。
| 检查项 | 预期结果 | 说明 |
|---|---|---|
| 引用共享 | user.tags 被外部修改 |
对象变量共享地址 |
| 浅拷贝边界 | 嵌套数组仍共享 | 展开只复制第一层 |
| 类型误判 | typeof null 为 "object" |
必须用恒等比较 |
| 常量不可重绑 | 赋值抛 TypeError |
const 约束绑定 |
讨论与局限。 浅拷贝无法处理深层嵌套与循环引用,structuredClone 能解决多数场景但不支持函数与 DOM 节点。另,const 数组仍可 push,需要真正不可变时用 Object.freeze,其只冻结一层。
5 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 改副本污染原数据 | 浅拷贝未覆盖嵌套层 | 逐层展开,或用 structuredClone |
| 判断数组失败 | 用了 typeof |
改用 Array.isArray |
null 判空后仍报错 |
只判 undefined |
用 v == null 同时排除两者 |
| 循环中变量值全一样 | 用了 var 且存在异步回调 |
改用 let 或 forEach |
| 数字精度异常 | 超过 2^53 的整数 |
用 BigInt 或字符串存储 ID |
6 结论
| 序号 | 结论 | 依据 |
|---|---|---|
| 1 | const 约束绑定,不约束内容 |
3.2 节代码 |
| 2 | 对象变量共享地址,复制需显式展开 | 图 3-1 |
| 3 | 类型判别按目标选方法,不用单一 typeof |
表 3-1 |
| 4 | 新代码只用 const 与 let |
3.2 节说明 |
- 上一篇:A01-认识JavaScript与运行环境
- 下一篇:A03-运算符与类型转换
- 返回:A00-总结 | 科目 README
参考文献
[1] MDN Web Docs · JavaScript 数据类型和数据结构,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Data_structures(访问于 2026-10-31)
[2] ECMA-262 · 6.1 ECMAScript Language Types,https://tc39.es/ecma262/#sec-ecmascript-language-types(访问于 2026-10-31)

评论 0
还没有评论,快来抢沙发吧~