目录
这篇文章系统地介绍了JavaScript函数与作用域的核心概念,主要内容包括:1. 函数本质与声明方式(封装代码块、洗衣机类比);2. 函数命名规范(小驼峰、动词前缀);3. 参数传递机制(形参/实参、默认值);4. 返回值原理(return双重作用);5. 作用域规则(全局/局部/嵌套、作用域链);6. 函数高级形态(具名/匿名/IIFE);7. 逻辑运算符短路规则;8. 类型转换机制与陷阱。文章强调函数作为独立执行单元的特性,并通过大量代码示例和类比说明关键概念,同时指出常见错误和调试技巧。
十、函数与作用域
10.1 函数本质与声明
核心定义:
封装可重复执行代码块的逻辑单元
类比:洗衣机(输入脏衣服+参数 → 执行清洗程序 → 返回干净衣服+返回值)
声明语法:
function 动词名() { // ⚠️ 必须使用 function 关键字
// 函数体(被"包裹"的代码)
}
执行特性:
1.函数体代码在调用前不会执行(类似洗衣机待启动)
2.调用后创建独立执行上下文(内部变量与外部隔离)
10.2 函数命名规范
规则类型 | 要求 | 示例 | 错误案例 |
格式规则 | 小驼峰命名法 | calculateTotal() | Calculate_total() |
前缀规范 | 动词开头 | getUserData() | userDataFetch() |
语义约定 | 常用动词表(强化可读性) |
常用动词前缀表:
动词 | 含义 | 应用场景 |
can | 判断可行性 | canEditContent() |
has | 判断包含关系 | hasPermission() |
is | 判断状态 | isValidEmail() |
get | 获取数据 | getCurrentTime() |
set | 设置值 | setFontSize() |
load | 加载资源 | loadImage() |
handle | 处理事件 | handleClick() |
10.3 函数调用机制
调用语法:函数名() ⚠️ 必须有括号
执行原理:
关键特性:
1.单次声明可无限次调用
2.每次调用创建新的执行上下文
3.与循环的区别:
特性 | 函数 | 循环 |
执行控制 | 按需调用 | 立即执行 |
作用域 | 独立上下文(变量隔离) | 共享外部作用域 |
10.4 函数参数详解
10.4.1 参数传递原理
形参(Formal Parameter):
function getSum(num1, num2) { // num1/num2是形参
return num1 + num2;
}
实参(Actual Parameter):
getSum(10, 20); // 10和20是实参
内存映射关系:
调用时:实参 → 赋值给 → 形参变量
[10] → num1
[20] → num2
10.4.2 默认参数值
问题场景:
getSum(); // num1=undefined, num2=undefined → NaN
解决方案:
function getSum(num1 = 0, num2 = 0) {
return num1 + num2;
}
执行规则:
1.有实参 → 使用实参值
2.无实参 → 使用默认值
3.部分实参 → 剩余参数用默认值
10.5 返回值核心机制
10.5.1 return 关键字
双重作用:
1.返回数据给调用者
2.立即终止函数执行(后续代码不运行)
function checkAge(age) {
if (age < 18) return "未成年"; // 此处终止
console.log("此代码不会执行");
return "成年人";
}
返回规则:
返回形式 | 结果 |
return 值; | 返回指定值 |
return; | 返回 undefined |
无 return | 返回 undefined |
10.5.2 返回值应用场景
// 场景1:直接使用返回值
let total = calculatePrice(100, 3); // 300
// 场景2:链式调用
getUserData().filter().map();
// 场景3:条件判断
if (isValid(input)) {
// 执行操作
}
10.6 参数与返回值实战案例
// 商品折扣计算函数
function applyDiscount(price, discount = 0.1) {
// 输入验证
if (typeof price !== 'number' || price <= 0) {
return "价格无效"; // 提前终止
}
// 计算折扣价
const finalPrice = price * (1 - discount);
// 返回结果对象
return {
original: price,
discountRate: discount,
final: finalPrice.toFixed(2)
};
}
// 调用示例
const result = applyDiscount(200, 0.2);
console.log(result);
/* 输出:
{
original: 200,
discountRate: 0.2,
final: "160.00"
} */
10.7 易错点与调试技巧
错误类型 | 案例 | 解决方案 |
忘记调用括号 | sayHi; (未执行) | sayHi(); |
参数个数不符 | getSum(10) (缺参数) | 使用默认参数值 |
return 后换行 | return\n x+y; → 返回undefined | 确保return与值在同一行 |
作用域混淆 | 函数内访问外部未声明变量 | 使用参数传递数据 |
调试工具使用:
1.在函数体内设置断点(Sources面板)
2.监控形参值(Watch窗口添加变量)
3.跟踪返回值(Console输出或Watch监控)
10.8 作用域深度解析
10.8.1 作用域层级
全局作用域:
1.生效范围:整个 <script> 标签或独立 JS 文件
2.生命周期:随页面加载创建,页面关闭销毁
局部作用域:
1.生效范围:函数内部(又称函数作用域)
2.生命周期:函数调用时创建,执行完毕销毁
嵌套作用域:
function outer() {
// 外层局部作用域
let a = 10;
function inner() {
// 内层局部作用域
console.log(a); // 访问外层变量
}
inner();
}
10.8.2 作用域链机制
访问原则:
"就近优先,逐级向上"
经典案例:
let globalVar = "全局";
function outer() {
let outerVar = "外层";
function inner() {
let innerVar = "内层";
console.log(innerVar); // "内层"(当前作用域)
console.log(outerVar); // "外层"(父作用域)
console.log(globalVar); // "全局"(全局作用域)
}
inner();
}
outer();
10.8.3 变量类型与规范
变量类型 | 声明位置 | 访问范围 | 生命周期 | 是否推荐 |
全局变量 | 函数外部 | 任何位置 | 页面打开到关闭 | 谨慎使用 |
局部变量 | 函数内部 | 当前函数内部 | 函数执行期间 | 推荐 |
隐式全局 | 未声明直接赋值 | 全局 | 同全局变量 | ❌ 禁止 |
特殊注意:
function dangerous() {
// 未使用let声明 → 成为隐式全局变量!
leakVar = "污染全局";
}
dangerous();
console.log(leakVar); // "污染全局"(应报错但实际可访问)
10.9 函数高级形态
10.9.1 具名函数
标准声明:
// 声明与调用分离
function calculate() { ... }
calculate();
核心特性:
函数提升(可在声明前调用)
sayHello(); // 正常执行(提升机制)
function sayHello() { console.log("Hi"); }
10.9.2 匿名函数
函数表达式:
// 匿名函数赋值给变量
const greet = function() {
console.log("你好!");
};
greet(); // 调用
特性对比具名函数:
特性 | 具名函数 | 匿名函数表达式 |
提升 | ✅ 可先调用后声明 | ❌ 必须先赋值后调用 |
调试 | 显示函数名 | 显示变量名或anonymous |
递归调用 | 可直接用函数名 | 需通过变量名 |
10.9.3 立即执行函数(IIFE)
语法结构:
// 标准形式
(function() {
console.log("立即执行");
})();
// 带参数版本
(function(name) {
console.log(`Hello ${name}`);
})("张三");
核心价值:
避免污染全局:
// 全局变量
let count = 0;
(function() {
// 局部变量,不污染全局
let privateCount = 10;
})();
封装私有数据:
const counter = (function() {
let count = 0; // 私有变量
return {
increment: function() { count++; },
get: function() { return count; }
};
})();
counter.increment();
console.log(counter.get()); // 1
console.log(count); // 报错!count未定义
分号警示:
// 错误!前一行缺少分号导致解析失败
let x = 10
(function() { ... })()
// 正确:前一行加分号
let x = 10;
(function() { ... })()
10.10 逻辑运算符短路规则
10.10.1 短路本质
触发条件:
运算符 | 短路条件 | 执行规则 | ||
&& | 左侧为假值 | 右侧不执行 | ||
` | ` | 左侧为真值 | 右侧不执行 |
假值列表:false, 0, "", null, undefined, NaN
10.10.2 短路应用
条件执行:
// 用户存在时执行操作
user && user.save();
// 等同于
if (user) user.save();
默认值设置:
// 若name为假值,使用"匿名"
const displayName = name || "匿名";
安全访问:
// 避免访问undefined属性
const street = user && user.address && user.address.street;
10.11 类型转换机制
10.11.1 隐式转换规则
操作类型 | 转换规则 | 示例 | 结果 |
字符串加法 | 任何类型 → 字符串 | "" + 1 | "1" |
数学运算 | 字符串 → 数字(失败为NaN) | "10" - 5 | 5 |
"pink" - 1 | NaN | ||
null转换 | 数学运算中 → 0 | null + 1 | 1 |
undefined转换 | 数学运算中 → NaN | undefined + 1 | NaN |
布尔转换 | 逻辑判断中自动转换 | if("hello") {...} | 执行代码 |
10.11.2 转换陷阱案例
console.log('' - 1); // -1 (空字符串→0)
console.log('pink' - 1); // NaN (非数字字符串)
console.log(null + 1); // 1 (null→0)
console.log(undefined + 1); // NaN (undefined→NaN)
console.log(NaN + 1); // NaN (任何数+NaN=NaN)