主题
1-17数据类型存储+变量声明const优先
本页对应 第一章JavaScript基础,共 2 段视频,约 23 分钟。
学习目标
- 能够区分基本数据类型与引用数据类型在栈、堆中的存储位置,并说明选择该存储方式的原因。
- 能够根据变量赋值代码,判断复制的是“值”还是“地址”,并正确预测最终输出结果。
- 能够解释
const声明数组或对象后,为什么可以修改其元素或属性,但不能重新赋值为新数组或新对象。 - 能够根据变量是否会被重新赋值,判断代码中应使用
let还是const,并完成合法改写。 - 能够运用“优先使用
const,需要重新赋值时改用let”的原则,检查并修正变量声明代码。
知识点
一、先看内存:栈和堆
为什么需要: 学数据类型不能只记“数字、字符串、对象”,还要知道它们在内存里大概放在哪里。转写稿里强调,内存可以划分成“栈”和“堆”两个区域:栈访问速度快,适合放简单的数据;堆存储容量大,适合放比较复杂的数据。理解这一点,后面才能解释变量赋值、对象修改为什么会出现不同结果。
怎么想: 不要一上来背结论,先抓住两个区域的特点:栈快、堆大。基本数据类型比较简单,放到栈里;引用数据类型(对象、函数、数组)比较复杂,真正的数据放到堆里。变量名通过栈中的空间找到值,或者找到堆中数据的地址。
怎么做: 可以结合下面两张示意图理解:基本数据类型直接把值放进栈中的小空间;引用数据类型在栈中放地址,地址指向堆中的真正数据。地址一般用十六进制表示,由系统自动分配,不需要我们手动记。


易错点: 不要说“对象存在变量里”。更准确地说,变量在栈中保存的是地址,对象本身在堆中。地址不用关心具体是多少,关键是理解“变量通过地址找到堆里的数据”。
二、基本类型赋值与引用类型赋值的区别
为什么需要: 只看概念容易糊涂,一写代码就会遇到“为什么改了 obj,obj2 也变了”的问题。转写稿用两道题推导:基本类型赋值给的是值,引用类型赋值给的是地址。
怎么想: 基本类型执行 let Y = X 时,是把 X 里的值复制给 Y,之后改 X 不影响 Y。引用类型执行 let obj2 = obj 时,复制的是栈中保存的地址,两个变量指向堆中同一个对象,所以改其中一个对象的属性,另一个也会看到变化。
怎么做: 用代码验证:
js
let X = 10;
let Y = X;
X = 20;
console.log(Y); // 10
let obj = { age: 18 };
let obj2 = obj;
obj.age = 20;
console.log(obj2.age); // 20第一段中,Y 拿到的是值 10,所以 X 改成 20 后 Y 仍是 10。第二段中,obj2 拿到的是 obj 保存的地址,二者指向同一个对象,所以 obj.age = 20 后,obj2.age 也是 20。
易错点: 不要把“引用类型赋值”理解成“复制了一个新对象”。这里复制的是地址。只要两个变量地址相同,修改对象内部属性就会互相影响。
三、变量声明:const 优先
为什么需要: 声明变量有 var、let、const。转写稿建议先排除较老、问题较多的 var,在实际开发中优先使用 const:很多变量声明时就知道后面不会被重新赋值,使用 const 更安全;React、Vue 中基本也常用 const。
怎么想: 不要死记“const 完全不能改”。本质是:const 声明变量的地址不能改。基本类型在栈里直接存值,所以值变了就不行;数组和对象是引用类型,栈里存地址,只要不换成新数组、新对象,修改内部元素或属性是可以的。
怎么做: 先给 const,发现后面要重新赋值再改成 let。例如只做相加、不再改值的变量可以用 const;number = number + 1、i++ 这种重新赋值的场景用 let。数组和对象可以这样写:
js
const arr = [1, 2];
arr.push(3);
console.log(arr); // [1, 2, 3]
const names = [];
names[0] = 1;
names[1] = 2;
const obj = {};
obj.color = "pink";但下面这种“换了一个新数组/新对象”的写法不行,因为地址变了:
js
const arr1 = [];
arr1 = [1, 2, 3]; // 报错:不能给常量重新赋值
const obj1 = {};
obj1 = {}; // 报错易错点: const 声明时通常要初始化;const 数组可以 push、可以按下标赋值,const 对象可以新增或修改属性,但不能重新赋值为另一个数组或对象。调试时可用 console.log 分别打印 Y、obj2.age、arr,再故意写一次重新赋值观察报错,就能把“值复制”和“地址复制”区分开。
四、专业术语表
为什么需要: 后面看文档、报错、读代码时会反复遇到这些词,先把叫法统一,沟通更准确。
怎么想: 关键字是 JS 中有特殊意义的词;保留字是目前没意义、未来可能用的词;标识符就是变量名、函数名的另一种叫法;表达式可以被求值,常配合运算符;语句是一段可执行代码。
怎么做: 保留笔记中的术语表:
| 术语 | 解释 | 举例 |
|---|---|---|
| 关键字 | 在 JavaScript 中有特殊意义的词汇 | let、var、function、if、else、switch、case、break |
| 保留字 | 在目前的 JavaScript 中没意义,但未来可能会具有特殊意义的词汇 | int、short、long、char |
| 标识(标识符) | 变量名、函数名的另一种叫法 | 无 |
| 表达式 | 可以被求值的代码,一般配合运算符出现 | 10 + 3、age >= 18 |
| 语句 | 一段可执行的代码 | if ()、for() |
易错点: 不要把“表达式”和“语句”混为一谈:表达式通常能产生一个值,语句更强调一段可执行代码。变量命名时也要避免使用关键字、保留字。
课件图示








代码示例
示例一:基本数据类型 —— 赋值传“值”(栈中存值)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>基本数据类型:赋值传值</title>
</head>
<body>
<script>
// 基本数据类型(number / string / boolean / null / undefined)存放在「栈」中,访问速度快
let x = 10; // 栈里开辟空间 x,直接把 10 存进去
let y = x; // 把 x 里的「值」10 复制一份给 y
x = 20; // 重新给 x 赋值,只改 x 自己那块空间
console.log('x =', x); // 20
console.log('y =', y); // 10 ← y 没变,因为复制的是值
// 值声明后不再修改时,优先用 const
const num1 = 10;
const num2 = 20;
console.log('num1 + num2 =', num1 + num2); // 30
</script>
</body>
</html>要点解释
let y = x这一步复制的是值本身,两个变量在栈中是两块独立空间,所以后面改x不影响y。- 栈的特点是访问速度快,适合存简单的、大小固定的基本数据类型。
- 变量声明优先
const:num1、num2只拿来参与运算,值从没被改过,用const更安全。
易错点
- 新手常凭直觉答
y是 20,其实只要发生的是“值的复制”,两个变量就彻底独立。 - 别把
var当习惯写法,本节明确先排除var,用let/const。
示例二:引用数据类型 —— 赋值传“地址”(栈存地址、堆存数据)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>引用数据类型:赋值传地址</title>
</head>
<body>
<script>
// 对象属于引用数据类型:栈里存的是「地址」,真正的数据在「堆」里
let obj1 = { age: 18 }; // 栈: obj1 -> 地址;堆: { age: 18 }
let obj2 = obj1; // 复制的是 obj1 里存的「地址」,两个变量指向同一个对象
obj1.age = 20; // 通过 obj1 改的是堆里那个对象
console.log('obj1.age =', obj1.age); // 20
console.log('obj2.age =', obj2.age); // 20 ← 跟着一起变
// 数组同理:也是引用数据类型
let arr1 = [1, 2];
let arr2 = arr1;
arr1.push(3);
console.log('arr2 =', arr2); // [1, 2, 3]
</script>
</body>
</html>要点解释
let obj2 = obj1复制的是地址(一串十六进制数,由系统自动分配,不用关心),所以obj1和obj2是同一个地址、指向同一个对象。- 堆的优点是存储容量大,适合存放数组、函数、对象这类复杂数据。
- 因此“改任何一个对象,另一个都会跟着变”,这是引用类型最典型的特征。
易错点
- 把引用类型也按“传值”理解,就会错答
obj2.age是 18。 - 别去记/手写那个地址值,它是内存自动生成的。
示例三:const 优先 —— 地址不变就能改内容
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>const 优先:数组与对象</title>
</head>
<body>
<script>
// ✅ 情况一:只改堆里的内容,栈里的地址没变 —— const 完全没问题
const arr = [1, 2];
arr.push(3); // 往数组里追加元素
arr[0] = 100; // 修改数组元素
console.log(arr); // [100, 2, 3]
const obj = { color: 'red' };
obj.color = 'pink'; // 修改已有属性
obj.size = 'L'; // 新增属性
console.log(obj); // { color: 'pink', size: 'L' }
// ❌ 情况二:换成了新数组 / 新对象 —— 地址变了,const 报错
const names = [];
try {
names = [1, 2, 3]; // TypeError: Assignment to constant variable.
} catch (e) {
console.log('报错:', e.message);
}
// ✅ 想往空数组里加值,用 push 或下标赋值,而不是重新赋值
const names2 = [];
names2.push(1);
names2[1] = 2;
console.log(names2); // [1, 2]
// ✅ 值会被重新赋值时,才改用 let
let number = 1;
number = number + 1; // 重新赋值 → 必须 let
console.log(number); // 2
for (let i = 0; i < 3; i++) {
console.log('for i =', i); // i 每次 ++ 都是重新赋值 → 用 let
}
</script>
</body>
</html>要点解释
const声明的常量不能重新赋值,本质上锁的是栈里的地址;只要地址不变,堆里的数组元素、对象属性随便增删改。- 结论:有了变量先给
const,发现后面确实要被重新赋值(如number = number + 1、for循环里的i++),再改成let。 - 实际开发中(React、Vue)绝大多数变量都用
const声明。
易错点
- 误以为
const声明的数组/对象“里面一点都不能动”——错,动内容可以,动地址(重新赋值)才报错。 const names = []; names = [1,2,3]是典型错误:空数组也是数组,也有自己的地址,再给一个新数组地址就变了。const声明时必须初始化,不能只写const a;。
常见错误
1. 用 const 声明数组/对象后整体替换,报 TypeError: Assignment to constant variable
- 现象:
const arr = [1,2]; arr = [3,4];直接报错,学员以为“const 连 push 都不能用”。 - 原因:
const锁的是栈里保存的地址;新数组是新地址,属于重新赋值,不是修改内容。 - 修法:改内容用
arr.push(3)、arr[0] = 1、obj.color = 'pink';确实要换成新数组/新对象时,把声明改为let。
2. const 声明却不初始化,报 SyntaxError: Missing initializer in const declaration
- 现象:
const age;或const list;直接语法报错。 - 原因:
const必须在声明时确定值(地址),之后不能再赋。 - 修法:声明时立即赋值
const age = 18;;若确实要“先声明后赋值”,用let。
3. 把“对象赋值”当成“复制一份”,改一个另一个也变,找不到原因
- 现象:
let o2 = o1; o1.age = 20;之后打印o2.age也是 20,学员以为代码写错。 - 原因:引用数据类型赋值复制的是栈里的地址,两个变量指向堆里同一个对象。
- 修法:画一遍“栈存地址→堆存数据”的图确认预期;需要互不影响时新建对象(如
{...o1})再改。
4. 反过来把基本数据类型当引用类型,以为改 x 会影响 y
- 现象:
let y = x; x = 20;之后担心y也变 20,答题犹豫。 - 原因:基本数据类型赋值复制的是“值”,两段栈空间互不相干。
- 修法:判断口诀先分类——是数字/字符串/布尔/null/undefined 就是值复制,是对象/数组/函数就是地址复制。
5. 变量名用了关键字或保留字、或以数字开头,报 SyntaxError: Unexpected token
- 现象:
let class = 1;、const int = 5;、let 1stName = 'a';报错。 - 原因:
class是关键字,int是保留字,标识符不能以数字开头。 - 修法:换成语义化合法标识符(
className、firstPlace),并养成看报错行号的习惯。
6. 把表达式当语句用(或反之),写进不该放的位置
- 现象:
console.log(if (age >= 18) {})、let x = if(...)报语法错误。 - 原因:
age >= 18、10 + 3是表达式(可求值),if () {}、for () {}是语句,语句不能作为值参与运算。 - 修法:需要“取值”的地方放表达式;需要“做流程控制”的地方写语句,二者不要互相嵌套错位。
练习
1. 预测输出并说明理由
js
let a = 10;
let b = a;
a = 20;
console.log(b); // ?再把它改成引用类型版本,说明为什么结果规律不同:
js
let o1 = { age: 18 };
let o2 = o1;
o1.age = 20;
console.log(o2.age); // ?2. 判断能否把 let 换成 const,并说明依据
js
// A
let n1 = Number(prompt('第一个数'));
let n2 = Number(prompt('第二个数'));
let sum = n1 + n2;
console.log(sum);
// B
let num = 1;
num = num + 1;
// C
let arr = ['red', 'green'];
arr.push('pink');
// D
let names = [];
names = ['张飞', '赵云'];3. 找错并改正
js
const price;
const list = [1, 2, 3];
list = [4, 5, 6];
const user = {};
user.name = '小明';指出哪几行会报错、报什么错、分别怎么改。
4. 需求实现 声明一个 const 对象 student = { name: '小红', age: 18 },在不改变其地址的前提下:把 age 改为 19、新增属性 gender: '女'。写完后说明为什么这里用 const 而不是 let。
5. 术语辨析 指出下面每一行的最小单位属于“表达式”还是“语句”,并指出其中出现的标识符:
js
let score = 90;
score >= 60;
if (score >= 60) { console.log('及格'); }原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 数据类型存储方式 | 13:04 |
| 2 | 变量声明const优先 | 10:37 |
小结
- 基本数据类型的值直接存在栈里,引用数据类型的地址存在栈里、真正的数据存在堆里;栈快、堆大,这是分配方式的根本依据。
- 变量赋值时先分类再判断:基本数据类型复制的是“值”,引用数据类型复制的是“地址”,这决定了后续改动会不会互相影响。
const约束的是栈里的地址不可更换,不是内容不可变;所以const声明的数组/对象可以随便改元素和属性,但不能整体替换成新的数组/对象。const必须声明即初始化,let可以先声明后赋值;var是旧写法,可直接淘汰。- 变量声明策略固定为:默认写
const,发现后面要重新赋值(如累加、i++、整体替换)再改成let。 - 数组和对象在真实项目中绝大多数操作是“增删改查元素”,因此优先用
const声明,可读性和安全性更好。 - 关键字有特殊语法含义不能用、保留字未来可能被启用也不要用,标识符要合法且语义化;表达式可求值,语句用于流程控制,两者不可混放。
- 遇到“值变了/没变”的输出题,不要靠猜,直接在纸上画出栈和堆两块区域、标出地址指向,答案自然唯一。