A02 变量与数据类型

2026-10-31 17:281 阅读累计 不足 1 分钟约 12 分钟JavaScript
AI 摘要30 秒读完

讲解 JavaScript 变量声明、七种原始类型与引用类型的判别方法及复制语义差异。

  • const 约束引用重新绑定而非内容修改,新代码只用 const 与 let
  • typeof 对数组与 null 返回 object,需用 Array.isArray 与 === null 判别
  • 对象变量共享堆地址,赋值与浅拷贝只断开第一层引用
  • Symbol 用于唯一属性名,BigInt 用于超 2^53 整数,均有限制
  • 深层拷贝用 structuredClone,真正不可变需 Object.freeze
AI 生成 · 仅供参考

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 验证与讨论

  1. 执行上述代码,确认 user.tags 长度为 3,说明引用复制生效。
  2. 把 copy 的展开写法改为 const copy = { ...user },再执行 copy.tags.push('x'),确认 user.tags 也被修改,说明浅拷贝只断开一层。
  3. 在控制台依次执行 typeof []、typeof null、Array.isArray([]),对照表 3-1。
  4. 在严格模式下给 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 节说明

参考文献

[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

0/2000

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