面向零基础读者的JavaScript核心教程。详细讲解变量声明与作用域机制、8大数据类型及其栈堆存储原理、常用运算符求值逻辑与类型转换规律,包含完整的代码示例与直观图解。
JavaScript零基础入门:变量、数据类型与运算符详解
时间:2026-11-06
浏览:87
文章目录
一、变量声明与作用域规范:let、const 与 var
在程序中,变量本质上是一块具有明确名称的内存空间引用,用于暂存、读取和更新数据。编写 JavaScript 代码时,我们使用声明关键字向运行环境申请创建变量。现代 JavaScript(ECMAScript 2015 / ES6 规范之后)提供了 let 与 const,并明确将老旧的 var 列为淘汰实践。
1. 选型原则:何时使用 const,何时使用 let
在实际编写代码时,变量声明遵循一条极简且安全的默认法则:优先声明为 const,仅在明确需要重新赋值时使用 let。
- const(常量声明):用于声明初始化后不再改变引用的变量。声明时必须立即赋初值。使用
const可以防止意外篡改,让代码意图更加明确,也是工业级代码审查的首要要求。 - let(变量声明):用于声明后续需要改变其值的变量,例如
for循环的计数器、累加求和变量或状态机标记。
// 声明常量:初始化后不可重新赋值
const maxRetries = 3;
const apiBaseUrl = "https://api.example.com";
// 声明可变变量:后续按需更新
let currentAttempt = 0;
currentAttempt += 1; // 合法更新
// 错误示范:尝试修改 const 会直接抛出 TypeError
// maxRetries = 5; // Uncaught TypeError: Assignment to constant variable.
2. 深度对比:为什么坚决淘汰 var
很多早期教程仍然使用 var,但现代工程已全面弃用。var 与 let/const 存在三个关键差异,这些差异直接决定了代码是否稳健:
- 作用域隔离能力不同:
var仅具备函数作用域,无法被if、for等代码块{ }限制,极易造成局部变量泄漏到全局。而let与const具备严格的块级作用域(Block Scope)。 - 变量提升(Hoisting)与暂时性死区:
var存在提升机制,变量在声明前就能被读取,此时其值为undefined,容易引发静默的逻辑缺陷。let和const同样会发生声明提升,但在执行到声明语句之前,变量处于暂时性死区(Temporal Dead Zone, TDZ)中,任何提前访问都会直接抛出ReferenceError,从而提前暴露代码书写顺序错误。 - 重复声明保护:在同一作用域内,
var允许对同一个变量名反复声明而毫无警告,易发生意外覆盖;而let和const严禁重复声明同名变量。
3. 变量命名与代码风格规范
编写清晰易读的代码离不开严谨的命名习惯:
- 命名法:普通变量与函数统一使用小驼峰命名法(camelCase),如
userAccount、isActiveStatus。 - 语义明确:变量名应明确表达其存储内容的含义,禁止使用无意义的单字母(除循环计数器
i、j外)。 - 合法字符规则:变量名必须以字母、下划线
_或美元符号$开头,后续可包含数字;不能以数字开头,不能包含连字符-或空格,且绝不能使用 JavaScript 保留字(如class、function、return等)。
二、数据类型分类与内存模型:基本类型与引用类型
JavaScript 是一门弱类型(动态类型)语言,变量在声明时无需指定类型,数据的类型完全由其实际存储的值决定。JavaScript 中共有 8 种内置数据类型,划分为两大阵营:基本数据类型(Primitive Types) 与 引用数据类型(Reference Types)。
| 分类 | 数据类型 | 代表性取值示例 | typeof 运算返回值 | 内存存储位置与特性 |
|---|---|---|---|---|
| 基本类型 (Primitive) |
Number |
42, 3.14, NaN, Infinity |
"number" |
存储在栈内存(Stack)中。 占用固定大小空间,值不可变(Immutable),按值直接复制。 |
String |
"hello", 'JS', `模板` |
"string" |
||
Boolean |
true, false |
"boolean" |
||
Undefined |
undefined(声明未初始化) |
"undefined" |
||
Null |
null(意图表示空对象指针) |
"object" (历史遗留缺陷) |
||
Symbol |
Symbol("id")(全局唯一标识) |
"symbol" |
||
BigInt |
9007199254740993n(大整数) |
"bigint" |
||
| 引用类型 (Reference) |
Object(含 Array, Function 等) |
{ a: 1 }, [1, 2], function() {} |
"object"(函数返回 "function") |
实体存储在堆内存(Heap)中。 栈内存中仅保存指向堆中地址的指针,按引用地址传递。 |
1. 内存模型差异带来的关键行为区别
为什么区分基本类型和引用类型至关重要?核心原因在于变量赋值和修改时的物理表现完全不同:
- 按值复制(基本类型): 当我们将一个基本类型变量赋值给另一个变量时,系统在栈内存中开辟一段全新的空间,并将数值完整复制一份。此后修改其中一个变量,对另一个变量毫无影响。
- 按引用复制(引用类型): 当我们将一个对象或数组赋值给另一个变量时,复制的不是对象实体本身,而是栈中保存的堆内存地址指针。此时两个变量实际上指向堆内存中的同一个对象实体。通过任何一个变量修改对象的内部属性,另一个变量读取到的内容也会同步改变。
// 场景 1:基本类型的独立性
let originNum = 100;
let copyNum = originNum; // 栈内存复制值 100
copyNum = 200;
console.log(originNum); // 输出 100,不受 copyNum 影响
// 场景 2:引用类型的共享指针
const userA = { name: "张三", age: 24 };
const userB = userA; // 复制的是堆内存地址指针
userB.age = 25; // 修改同一块堆内存中的属性
console.log(userA.age); // 输出 25!userA 的属性同步被修改
使用 typeof null 时,返回的结果是 "object"。这是 JavaScript 最初实现时的历史遗留设计缺陷(当时利用前三位判断类型,对象的类型标签是 000,而 null 指针全是 0),但在现代规范中为保证向后兼容性予以保留。
因此,判断一个变量是否为纯粹的普通对象时,务必先排除 null;判断数组时,应使用官方专门提供的 Array.isArray(val),或者使用通用的精准检测方式:Object.prototype.toString.call(val)。
三、运算符与表达式:计算逻辑与类型转换
运算符用于对一个或多个操作数进行处理并返回计算结果。掌握运算符不仅需要了解它们的语法,更要透彻理解在运算过程中发生的隐式类型转换(Type Coercion)。
1. 算术运算符与加号的双重语义
JavaScript 提供了常规的算术运算符:加法 +、减法 -、乘法 *、除法 /、取模(取余)% 以及指数 **。
其中,加号 + 具有双重语义:当任一操作数为字符串时,加号退化为字符串拼接运算符;只有当两个操作数均为数值(或可隐式转为数值的非字符串值)时,才执行数学加法。而减法、乘法、除法等运算符则始终严格尝试将两端转换为数值再执行运算。
const res1 = 5 + 10; // 15 (数学加法)
const res2 = 5 + "10"; // "510" (字符串拼接:5 被隐式转为 "5")
const res3 = "20" - 5; // 15 (数学减法:字符串 "20" 隐式转为数字 20)
const res4 = "abc" * 2; // NaN ("abc" 无法转为有效数值,结果为 Not-a-Number)
2. 比较运算符:全等 === 与宽松比较 == 的本质区别
比较运算符用于对比两个操作数并返回布尔值(true 或 false)。初学者最容易踩坑的是宽松相等 == 与 全等(严格相等)=== 的区别。
- 全等运算符(===):首先比较类型,类型不同直接判定为
false;类型相同时才进一步比较数值。绝不进行隐式转换,行为完全可预测。 - 宽松相等(==):在比较前,若两端类型不一致,会按照一套复杂的 ECMAScript 规范将操作数强制隐式转换为同类再行比较,常常产生违背直觉的结果。
宽松相等会带来诸如 0 == "" 返回 true、false == "0" 返回 true 以及 [] == false 返回 true 等严重违背业务常识的陷阱。现代前端工程规范(如 ESLint 规则 eqeqeq)要求全项目禁止出现 == 与 !=,必须一律使用 === 与 !==。
3. 逻辑运算符与短路求值(Short-circuit Evaluation)
逻辑运算符包括逻辑与 &&、逻辑或 || 和逻辑非 !。在 JavaScript 中,它们不仅用于布尔条件判断,还广泛利用短路求值特性来处理业务赋值:
- 逻辑与
&&(遇假即止):从左往右求值。如果左侧表达式转换为布尔值为false(即假值:false,0,"",null,undefined,NaN),则直接返回左侧操作数的值;若左侧为真,则返回右侧操作数的值。常用于前置条件守卫。 - 逻辑或
||(遇真即止):从左往右求值。如果左侧为真值,则直接返回左侧操作数;若左侧为假值,则返回右侧操作数。常用于设置备选值。 - 空值合并运算符
??:由于||会将0和空字符串""视作假值并替换,ES2020 引入了??。它仅当左侧操作数为null或undefined时才会返回右侧值,完美解决了对数值0的误伤问题。
// 1. 逻辑与 && 作为条件守卫
const userProfile = { isLogin: true, nickname: "小明" };
userProfile.isLogin && console.log("欢迎回来:" + userProfile.nickname);
// 2. || 与 ?? 在默认值场景下的关键差异
const userConfigScore = 0; // 用户实际得分为 0
const displayScore1 = userConfigScore || 60;
// 结果为 60!因为 0 被 || 误判为假值,导致合理的分数被错误覆盖
const displayScore2 = userConfigScore ?? 60;
// 结果为 0!正确保留了用户的有效输入,只有 null 或 undefined 才会退回 60
四、常见问题排查与知识体系总结
1. 初学者高频困惑排查
-
NaN 为什么不等于自身?
根据 IEEE 754 浮点数标准,NaN(Not-a-Number)表示一个无法表示的无效数学运算结果。两个无效的计算结果不能被认定为相等,因此表达式NaN === NaN返回false。排查变量是否为 NaN 时,切勿使用全等比较,必须调用Number.isNaN(val)。 -
0.1 + 0.2 为什么等于 0.30000000000000004?
计算机底层采用二进制双精度浮点数(64位)存储数字,十进制的小数0.1和0.2转换为二进制时是无限循环小数,由于尾数位数有限导致截断舍入,相加后再转回十进制便产生了微小的精度误差。在货币或精确比对场景中,可将数值放大为整数(例如以“分”为单位)计算,或使用Math.abs(a - b) < Number.EPSILON确认两数是否在误差允许范围内相等。 -
undefined 与 null 的语义边界是什么?
undefined表示“缺少值”,即此处本该有值但尚未被定义或赋值(例如声明了未初始化的变量、函数未传参、对象不存在的属性);而null表示“无值”,是开发者主动赋予的语义标记,表示该变量有意被置空或表示不存在的对象引用。
掌握变量的作用域与选型、理解基本类型与引用类型的栈堆物理内存分配,以及恪守全等运算与严格短路逻辑,构成了编写专业 JavaScript 代码不可或缺的底层基石。初学者在后续深入学习函数、异步编程与 DOM 交互时,这些核心认知将持续提供清晰的分析支点。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。