JavaScript基础语法示例:变量声明、循环结构与函数定义
深入解析JavaScript核心基础语法,涵盖var/let/const变量声明机制、各类循环结构的应用选型与执行原理,以及函数定义的多种形式与实践避坑指南。
在 JavaScript 程序开发中,数据状态的存储、控制流的重复执行以及业务逻辑的封装抽象,分别由变量声明、循环结构与函数定义三个核心语法构件承载。掌握这三者的底层运作机制与编写规范,是编写出健壮、可维护代码的前提。本文将针对变量声明机制(var、let、const)、常见循环结构(for、while、for...of、for...in)以及函数定义范式展开细致讲解,配以完整可执行的代码示例,并剖析实际编码中的边界与常见避坑点。
一、变量声明机制与作用域规则
JavaScript 提供了三种变量声明关键字:传统的 var,以及 ECMAScript 2015 (ES6) 引入的 let 与 const。选择哪种关键字,直接决定了变量的生命周期、作用域范围以及可变性特征。
1. 作用域层级:函数作用域与块级作用域
var 遵循函数作用域规则,在包含它的函数外部不可见,但在代码块(如 if、for 等大括号结构)内声明的 var 变量会“穿透”到外层代码。相反,let 和 const 具备严格的块级作用域,只在声明它们的一对花括号 { ... } 内部有效。
// 1. var 的块级穿透问题
if (true) {
var userAge = 25;
}
console.log(userAge); // 输出: 25(变量穿透到块外部)
// 2. let 与 const 的块级隔离
if (true) {
let blockScore = 98;
const blockMax = 100;
}
// console.log(blockScore); // 报错: ReferenceError: blockScore is not defined
// console.log(blockMax); // 报错: ReferenceError: blockMax is not defined
2. 变量提升与暂时性死区(TDZ)
使用 var 声明的变量在代码执行前会被提升(Hoisting)到所在作用域顶部,并且被默认赋初始值 undefined,因此在声明前访问不会抛出致命错误。而 let 和 const 虽然也会在语法分析阶段被引擎获知,但在执行到声明语句之前,该变量均处于“暂时性死区”(Temporal Dead Zone, TDZ)中,任何提前读取或写入操作都会直接抛出 ReferenceError。
// var 的提升表现
console.log(title); // 输出: undefined(已提升,但未执行赋值)
var title = "ECMAScript 入门指南";
// let 的暂时性死区表现
// console.log(subtitle); // 抛出 ReferenceError: Cannot access 'subtitle' before initialization
let subtitle = "现代前端开发基石";
3. const 的不可变边界
const 用于声明常量,声明时必须立即提供初始值。需要特别澄清的是:const 保证的是变量标识符指向的内存地址不变,而不是该地址所存储的数据本身不可变。对于基本数据类型(如数字、字符串、布尔值),其值无法被修改;但对于对象和数组等引用类型,对象的内部属性或数组元素完全允许被增删改动。
const PORT = 8080;
// PORT = 9000; // 报错: TypeError: Assignment to constant variable.
const appConfig = {
env: "production",
timeout: 3000
};
// 允许修改内部属性:内存地址未发生改变
appConfig.timeout = 5000;
appConfig.logLevel = "warn";
console.log(appConfig); // 输出: { env: 'production', timeout: 5000, logLevel: 'warn' }
// 若需要完全冻结对象,需结合 Object.freeze
const frozenConfig = Object.freeze({ status: "locked" });
// frozenConfig.status = "open"; // 严格模式下抛出 TypeError,普通模式下静默忽略
二、循环结构语法与场景选型
循环用于重复执行具有相似规律的操作逻辑。JavaScript 提供了多种循环机制,针对计次循环、动态条件推进以及不同数据结构(数组、对象、Set、Map)的遍历,应根据语义和性能合理挑选。
1. 标准 for 循环:确定次数的精细控制
标准 for 循环通过“初始化语句、条件判断语句、递增/递减语句”三个表达式进行驱动,适合已知循环次数、或需要在遍历中通过步长跳跃操作索引的场景。
// 遍历数组并按步长累加
const metrics = [10, 25, 40, 55, 70, 85];
let evenIndexedSum = 0;
for (let i = 0; i < metrics.length; i += 2) {
evenIndexedSum += metrics[i];
}
console.log(evenIndexedSum); // 输出: 120 (10 + 40 + 70)
2. while 与 do...while 循环:条件驱动循环
while 在进入循环体前先评估条件表达式,若条件为假则一次也不执行;而 do...while 先无条件执行一次循环体,随后再判定条件。它们非常适用于直到满足特定业务条件才终止的动态逻辑(如网络重试、链表遍历、生成随机唯一标识等)。
// 模拟接口重试逻辑(while)
let retryCount = 0;
const maxRetries = 3;
let requestSuccess = false;
while (!requestSuccess && retryCount < maxRetries) {
retryCount++;
// 模拟在第2次尝试时成功
if (retryCount === 2) {
requestSuccess = true;
}
}
console.log(`重试结束,总次数: ${retryCount}, 结果: ${requestSuccess}`); // 输出: 重试结束,总次数: 2, 结果: true
3. for...of 与 for...in:可迭代对象与对象属性遍历
这是初学者最容易产生混淆的两个结构:
for...of:面向可迭代对象(Iterable)(如 Array、String、Map、Set、arguments),直接遍历获取容器中的元素值(Value)。支持break、continue与return。for...in:面向任意对象,遍历其自身及原型链上所有可枚举属性名(Key)。由于遍历顺序依据特定规范、且会扫描原型链,在遍历普通数组时应严格避免使用for...in。
// 1. for...of 遍历数组元素
const fruits = ["苹果", "香蕉", "橙子"];
for (const item of fruits) {
if (item === "香蕉") continue; // 跳过香蕉
console.log(item); // 分别输出: 苹果, 橙子
}
// 2. for...in 遍历对象键名(配合 Object.hasOwn 过滤继承属性)
const device = { brand: "ThinkPad", ram: "32GB", price: 8999 };
for (const key in device) {
if (Object.hasOwn(device, key)) {
console.log(`${key}: ${device[key]}`);
}
}
三、函数定义范式与执行机制
函数是 JavaScript 处理业务逻辑、构建模块化代码的一等公民(First-Class Citizens)。函数的定义形式经历了从早期声明式、表达式到现代箭头函数的演化,不同定义方式在作用域绑定和底层执行栈中有着本质差别。
1. 函数声明(Function Declaration)
函数声明通过 function 关键字配合函数名创建。其最关键的特性是函数声明整体提升:在作用域编译阶段,函数名和函数体整体被提升到作用域顶部,因此即便将函数调用写在声明语句之前,代码也能正确运行。
// 调用发生于声明之前:有效正常执行
const area = calculateCircleArea(5);
console.log(area); // 输出: 78.5398...
function calculateCircleArea(radius) {
return Math.PI * radius * radius;
}
2. 函数表达式(Function Expression)
函数表达式将一个匿名或具名函数赋值给一个变量。此时函数的初始化受制于变量声明的规则(若使用 const/let 则受 TDZ 限制,若使用 var 则提升为 undefined),因此绝不能在赋值语句执行之前调用。
// calculateDiscount(100); // 抛出错误:ReferenceError: Cannot access before initialization
const calculateDiscount = function(amount, rate) {
return amount * (1 - rate);
};
console.log(calculateDiscount(200, 0.15)); // 输出: 170
3. 箭头函数(Arrow Function)及其词法 this 约束
ES6 引入的箭头函数提供了更加紧凑的语法,在回调函数、高阶函数(如 map、filter)中广泛使用。但开发者必须牢记箭头函数的四个关键技术限制:
- 无独立
this绑定:箭头函数的this并非在运行时决定,而是在定义时捕获其外层词法作用域的this。 - 不能作为构造函数:无法通过
new关键字实例化,也没有prototype属性。 - 无
arguments对象:如需处理不确定数量的实参,必须使用剩余参数(Rest Parameters)...args。 - 不能用作生成器(Generator):语法不支持
yield命令。
// 1. 箭头函数语法简写与剩余参数
const sumAll = (...numbers) => numbers.reduce((acc, val) => acc + val, 0);
console.log(sumAll(1, 2, 3, 4, 5)); // 输出: 15
// 2. this 指向对比
const timerService = {
name: "订单轮询器",
start: function() {
// 普通函数中的 this 指向调用者 timerService
console.log(`服务启动: ${this.name}`);
// 箭头函数继承外层 start 的 this
setTimeout(() => {
console.log(`心跳维持: ${this.name}`); // 正确输出: 心跳维持: 订单轮询器
}, 100);
}
};
timerService.start();
四、综合实战:电商订单明细清洗与金额核算
为了让读者直观理解变量声明、循环结构与函数定义如何协同构建实际功能,我们通过一个完整的业务案例进行演示:处理一批结构松散的电商原始订单数据,对其进行过滤、金额折算并归类汇总。
1. 业务目标与需求约束
- 使用
const锁定不可变配置常量(税率、起送阈值等); - 封装具备默认参数与输入校验的纯函数进行单项金额核算;
- 采用现代
for...of循环实现对订单列表的高效安全迭代,避免传统循环下标越界风险; - 在遍历过程中使用
continue剔除无效订单,利用局部let维护累加状态。
2. 完整业务代码实现
/**
* 电商订单处理模块示例
* 演示:const 声明常量、箭头函数计算、for...of 循环过滤与汇总
*/
// 1. 常量声明:业务基准配置
const DEFAULT_TAX_RATE = 0.06; // 基础税率 6%
const MIN_ORDER_THRESHOLD = 10; // 有效订单最低金额
// 2. 函数定义:单项实付计算(带默认参数的纯函数)
const computeItemTotal = (price, quantity = 1, discount = 0) => {
if (price < 0 || quantity <= 0) {
return 0;
}
const subtotal = price * quantity;
const finalAmount = subtotal * (1 - discount);
return Number(finalAmount.toFixed(2));
};
// 3. 原始数据源(实际业务中通常来自 API)
const rawOrders = [
{ id: "ORD-101", item: "机械键盘", price: 399, qty: 1, discount: 0.1, status: "PAID" },
{ id: "ORD-102", item: "防尘鼠标垫", price: 8, qty: 1, discount: 0, status: "PAID" }, // 低于阈值
{ id: "ORD-103", item: "4K显示器", price: 1899, qty: 2, discount: 0.05, status: "CANCELLED" }, // 已取消
{ id: "ORD-104", item: "人体工学椅", price: 1299, qty: 1, discount: 0.15, status: "PAID" }
];
// 4. 主流程函数:负责循环遍历与汇总输出
function generateFinancialReport(orderList) {
// 状态累加变量使用 let 明确限定在函数体内
let totalRevenue = 0;
let processedCount = 0;
const validOrderIds = [];
// 采用 for...of 遍历数组元素
for (const order of orderList) {
// 守卫条件:过滤已取消订单
if (order.status !== "PAID") {
continue;
}
const itemNet = computeItemTotal(order.price, order.qty, order.discount);
// 守卫条件:剔除不足最低结算门槛的订单
if (itemNet < MIN_ORDER_THRESHOLD) {
continue;
}
// 计入汇总状态
totalRevenue += itemNet;
processedCount++;
validOrderIds.push(order.id);
}
const taxAmount = Number((totalRevenue * DEFAULT_TAX_RATE).toFixed(2));
const grandTotal = Number((totalRevenue + taxAmount).toFixed(2));
return {
orderCount: processedCount,
validOrderIds,
netRevenue: totalRevenue,
taxAmount,
grandTotal
};
}
// 5. 执行报告输出
const report = generateFinancialReport(rawOrders);
console.log("财务结算结果:", report);
/*
输出示例:
{
orderCount: 2,
validOrderIds: [ 'ORD-101', 'ORD-104' ],
netRevenue: 1463.25,
taxAmount: 87.80,
grandTotal: 1551.05
}
*/
1. rawOrders 与 report 被定义为 const,保证对象和数组引用指针的稳定;
2. totalRevenue 与 processedCount 为运行中反复累加的标量数字,必须声明为 let,且严格限制在 generateFinancialReport 函数作用域内,杜绝全局变量污染;
3. for (const order of orderList) 每次循环都产生一个独立的块级上下文,因此循环项 const order 是完全合法且推荐的写法,防止单次迭代对上一轮变量发生意外复用。
五、全景总结与规范速查清单
为了帮助开发者在日常编码中形成确定性的习惯,我们将变量声明、循环选择与函数定义的关键逻辑归纳为如下综合速查图谱与规范清单。
3. 日常编码检查清单(Checklist)
| 检查维度 | 推荐做法 | 反模式(避免) |
|---|---|---|
| 变量声明 | 始终优先使用 const;仅当标识符需要被重新赋值时降级为 let。 |
在现代项目中书写任何 var,或声明变量时遗漏关键字导致隐式全局变量。 |
| 可变引用 | 若希望对象或数组内容也不可被篡改,声明为 const 的同时调用 Object.freeze()。 |
误以为 const arr = [] 会阻止 arr.push() 等变更操作。 |
| 集合遍历 | 遍历数组使用 for...of;遍历对象的键值对使用 Object.entries() 配合 for...of。 |
使用 for...in 遍历普通数组,导致原型链属性与非数字索引污染。 |
| 迭代中断 | 需要在满足条件时提早退出循环时,使用 for / for...of 搭配 break 或 continue。 |
在 Array.prototype.forEach 中试图通过 return 跳出整个循环。 |
| 函数与 this | 数组变换管道和行内回调优先使用箭头函数;组件或对象的方法属性使用常规函数或方法简写。 | 在对象的方法声明上使用箭头函数,导致方法内部的 this 逃逸绑定到全局。 |
通过建立对变量作用域隔离、循环控制语义以及函数执行环境的清晰认知,开发者能够在编写 JavaScript 逻辑时自然规避绝大多数隐蔽的状态错乱与内存隐患,为构建高质量 Web 应用打下坚实的语法根基。
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19








