02变量与数据类型

JavaScript 是「弱类型」语言——变量本身没有类型,值才有类型。 同一个变量,今天装字符串、明天装数字,没人拦你。 这一课把声明方式 var / let / const 三兄弟和 7 种原始类型 + 1 个对象类型一次讲清, 把代码里"变量"和"类型"两个最容易混的概念钉在脑子里。

约 18 分钟 6 个动手实验 let / const + 7 类型

一、三种声明:var / let / const

声明一个变量,就是告诉浏览器"我占个坑、以后会往里塞值"。 三种声明方式各有脾气,现代项目里默认用 const, 需要改的才用 let,var 几乎绝迹。

var
函数作用域 · 可重复声明 · 会变量提升
let
块作用域 · 不可重复声明 · 可修改
const
块作用域 · 声明必须赋值 · 不可重新赋值
三种声明方式对照:作用域 / 重复声明 / 提升 / 可修改
关键字作用域可重复声明变量提升可修改值现代态度
var函数作用域✓ 可以✓ 提升到顶✓ 老代码遗产,新项目基本不用
let块作用域 { }✗ 报错TDZ 暂时性死区✓ 默认值会变的变量
const块作用域 { }✗ 报错TDZ 暂时性死区✗ 不可重新赋值 现代项目默认值
点运行看三块结果:① var 出了块照样能用;② let 在块外访问会报错;③ const 重新赋值立刻炸——把三个 console.log 改成你想要的变量名
const 不是说"对象不能改属性"。 const obj = { a: 1 } 之后 obj.a = 2 完全合法—— 它管的是「绑定关系」,不管「对象内容」。 想让对象连属性也锁死,用 Object.freeze()。

二、命名规范与提升(hoisting)

命名三选一:小驼峰 userName(变量/函数首选)、 下划线 _internal(约定为「私有」)、大驼峰 UserCard(类/构造函数)。 三个硬规矩:不能数字开头、不能用保留字、要见名知意。

变量提升(hoisting)也是个大坑:var 声明会被「提到作用域顶端」, 所以可以先使用再声明(值是 undefined);而 let / const 不提升, 在声明之前使用会进入「暂时性死区」(TDZ)直接报错。

点运行对比 TDZ:① var 提前打印是 undefined(不报错);② let 在声明前使用立即抛 ReferenceError
TDZ(暂时性死区)不是 bug,是设计: let / const 在声明前的存在窗口是"看见了但用不了"—— 引擎逼你先声明再使用,从此告别"先斩后奏"带来的诡异 bug。

三、原始类型 7 种 + typeof

JS 把值分成两大家族:原始类型(primitive,7 种) 和 对象类型(object)。 原始类型是「不可再拆」的原子值,对象类型是「可拆」的有结构数据。

number42 / 3.14 / NaN
string"前端" / 'JS'
booleantrue / false
null「空」的故意值
undefined「未定义」的默认
symbol唯一标识 (ES6)
bigint超长整数 (ES2020)
object对象 / 数组 / 函数…
七种原始类型速查:类型 / 字面量 / typeof 返回
类型典型字面量typeof 返回一句话
number42 / 3.14 / NaN"number"整数小数都算它,NaN 也算
string"前端" / 'JS' / `a`"string"单引、双引、反引三种引号都成
booleantrue / false"boolean"是 / 否,开 / 关
nullnull"object" ⚠「故意为空」,开发者明确说"这里没值"
undefinedundefined"undefined"「默认空」,未赋值时的占位
symbolSymbol("id")"symbol"每次调用都生成唯一值,常用于对象 key
bigint9007199254740993n"bigint"数字后面加 n,突破 number 精度上限
点运行一次打印七种类型的 typeof:注意 null 返回的是 "object"(JS 历史 bug)
typeof null === "object" 是 JS 史上最著名的 bug: 2000 年前就存在,至今为了兼容性仍不能修。判断「是不是 null」必须用 val === null,绝不能信 typeof。

四、引用类型:对象 / 数组 / 函数

除了 7 种原始值,其他全是引用类型(object): 普通对象、数组、函数、日期、正则… 它们存的不是值本身,是地址(指针)—— 复制过去的是同一张门卡,谁改都看得见。

typeof 对引用类型的判定:四个常用结果
值typeof 返回类别
{}"object"普通对象
[]"object"数组(Array.isArray() 才是真判法)
function(){}"function"函数(对象里唯一 typeof 不是 object 的)
null"object"「空对象指针」—— 历史遗留
点运行看引用类型的关键性质:① 对象赋值是「共享门卡」,改 b.name 同时也改了 a.name;② 数组是对象(typeof 返回 object)
想复制一份"互不干扰"的对象:用浅拷贝 Object.assign({}, src) / {...src},或者 深拷贝 structuredClone(src)(深到嵌套对象)。 这是后面讲「引用陷阱」和「状态管理」的起点。

五、字符串模板与常用方法

字符串用 ' ' / " " / ` ` 包起来都行, 但反引号(模板字符串)是唯一能直接嵌入变量和换行的写法:

字符串 9 个常用方法速查
方法作用示例
length取长度(属性,非方法)"前端".length → 2
indexOf(s)查位置(找不到 -1)"前端自习".indexOf("自习") → 2
slice(a, b)截取 a 到 b(不含 b)"hello".slice(1,4) → "ell"
split(s)按 s 切成数组"a,b,c".split(",") → ["a","b","c"]
replace(a, b)替换首个匹配"hi".replace("h","H") → "Hi"
trim()去首尾空白" hi ".trim() → "hi"
toUpperCase()转大写"js".toUpperCase() → "JS"
includes(s)是否包含 s"前端".includes("端") → true
startsWith(s)是否以 s 开头"https://".startsWith("http") → true
在输入框打字,下面实时显示三件事:① 模板字符串拼接的完整句子;② 大写结果;③ 前 5 个字符——改 name 默认值成你自己的名字
字符串在 JS 里是不可变(immutable)的: 所有方法都返回新串,原串纹丝不动。所以 "abc".replace("a","x") 不会改变 "abc" 本身——必须接住返回值。

六、数字与数学

JS 的 number 只有一个类型——IEEE 754 双精度浮点, 整数和小数共用。这意味着浮点运算有精度陷阱: 0.1 + 0.2 不等于 0.3,而是 0.30000000000000004。

点按钮三连发:① 看 0.1+0.2 的真面目;② 取 1~100 随机整数;③ 四舍五入 4.5 / 4.4 / -1.5
0.1 + 0.2 !== 0.3 是 IEEE 754 二进制浮点决定的, 任何语言都会遇到(Python / Java / Go 一样)。处理小数业务: ((0.1 * 10 + 0.2 * 10) / 10).toFixed(2)、 或装 decimal.js / big.js。 前端最常见的场景——价格展示——务必用「分」做单位、整数运算。

七、类型转换:隐式 vs 显式

JS 在某些场景会自动把值「掰弯」到想要的类型,叫隐式转换; 想自己控,就用显式转换函数。理解这套规则, 就能看懂 "5" + 3 = "53" 到底是字符串还是数字。

类型转换规则速查:场景 / 行为 / 结果
场景行为示例
+ 遇字符串另一边转字符串后拼接"5" + 3 → "53"
- * /两边都转数字"5" - 3 → 2
==(双等)类型不同先转再比"5" == 5 → true(少用)
===(三等)类型不同直接 false"5" === 5 → false(推荐)
Number(x)整体转数字,不能转则 NaNNumber("abc") → NaN
String(x)整体转字符串String(42) → "42"
Boolean(x)0 / "" / null / undefined / NaN → false,其它 trueBoolean("") → false
parseInt / parseFloat从头读数字,读不到就停parseInt("42px") → 42
点运行看四组对照:① "5"+3 → "53";② "5"-3 → 2;③ Boolean("") → false;④ Number("abc") → NaN —— 把 "5" 换成 "5.5" 看 - 的浮点处理
判断相等永远用 === / !==。 == 看着省事("5" == 5 也成立), 但它的隐式转换规则有 8 条边缘情况等你踩坑。 现代代码里 三等是标配,唯一例外是和 null 比:val == null 会同时命中 null 和 undefined,是个常用惯用法。

八、本章小结

  • 默认用 const,需要改的才用 let,var 几乎不用——块作用域是底线。
  • 命名用小驼峰,不能数字开头、不能用保留字、要见名知意。
  • let / const 不提升(TDZ),声明前使用直接 ReferenceError。
  • 7 种原始类型 + 1 个对象类型,typeof 是诊断工具,但 typeof null 是 object(历史 bug)。
  • 引用类型存的是地址,赋值是「共享门卡」——改一处、到处变;想互不干扰用浅/深拷贝。
  • 字符串是不可变的,所有方法返回新串;反引号模板字符串是拼接主力。
  • number 是双精度浮点,0.1 + 0.2 !== 0.3,钱用「分」算。
  • 类型转换:+ 拼接、- 算术;判断相等用 ===;空串/0/NaN/null/undefined 五个是「假值」。
动手练习: ① 声明一个 const obj = { name: "前端", age: 2 },把 name 改成你的名字,控制台打印对象; ② 用 console.log 输出 0.1 + 0.2 的真实结果,再用 toFixed(2) 修正; ③ 写一段模板字符串问候:`你好,${name}!今天是第 ${lesson} 课`; ④ 一组类型转换:"5" + 3、"5" - 3、Boolean("")、Number("42px")、parseInt("42px"),逐个 console.log 看结果; ⑤ 试着用 slice / split / includes 处理一段真实字符串(如 "前端-基础-02-变量" 用 split("-") 切出章节号)。