let fn = function() {} 和 function fn() {} 虽然都创建了一个函数,但在行为上有关键区别。
核心区别在于:函数声明会被提升,而函数表达式不会。
核心区别对比
| 特性 | function fn() {} | let fn = function() {} |
|---|---|---|
| 声明方式 | 函数声明 | 函数表达式 |
| 是否存在提升 | 是 - 整个函数声明(包括函数体)都会被提升到作用域顶部。 | 否 - 变量声明 (let fn) 会提升,但初始化(赋值为函数)不会。在初始化之前访问会报错。 |
| 初始化时机 | 在代码执行前,解析阶段就已经创建。 | 代码执行到赋值语句时才会初始化。 |
| 在初始化前访问 | 可以正常调用,因为函数已经存在。 | 会抛出 ReferenceError(引用错误),因为处于“暂时性死区”。 |
| 作用域 | 存在于其被定义的整个函数作用域或全局作用域中。 | 遵循其变量(let)的作用域规则。 |
代码示例详解
1. 提升行为的差异
函数声明 - 可以提前调用
javascript
// 可以正常运行
console.log(sayHello()); // 输出: "Hello!"
function sayHello() {
return "Hello!";
}
代码执行时,由于函数声明被提升,实际上相当于:
javascript
function sayHello() {
return "Hello!";
}
console.log(sayHello()); // "Hello!"
函数表达式 - 提前调用会报错
javascript
// 这段代码会报错:ReferenceError
console.log(sayHello()); // Uncaught ReferenceError: Cannot access 'sayHello' before initialization
let sayHello = function() {
return "Hello!";
};
代码执行时,由于let变量的暂时性死区,实际上相当于:
javascript
let sayHello; // 声明被提升,但未初始化,处于“暂时性死区”
console.log(sayHello()); // 尝试在初始化前调用,报错
sayHello = function() { // 此时才初始化
return "Hello!";
};
2. 在条件语句中的差异
函数声明在条件语句中的行为不确定(非严格模式)
不同JavaScript引擎对块级作用域内的函数声明处理不一致,不推荐在条件语句中使用函数声明。
javascript
// 不可靠的写法,行为因环境和严格模式而异
if (true) {
function foo() { console.log('A'); }
} else {
function foo() { console.log('B'); }
}
foo(); // 在不同浏览器中结果可能不同
函数表达式在条件语句中是安全且推荐的
javascript
// 可靠且安全的写法
let foo;
if (true) {
foo = function() { console.log('A'); };
} else {
foo = function() { console.log('B'); };
}
foo(); // 总是输出 "A"
// 或者更简洁的写法
const bar = true ? function() { console.log('A'); } : function() { console.log('B'); };
bar(); // 总是输出 "A"
总结与建议
| 场景 | 推荐 |
|---|---|
| 通用情况 | 两者均可,根据团队习惯选择。函数声明更简洁,提升特性有时很方便。 |
| 需要条件性定义函数时 | 务必使用函数表达式,它的行为是明确和可靠的。 |
| 需要遵守“先定义后使用”的严格规范时 | 使用函数表达式,可以避免由提升引起的意外行为,使代码执行流程更清晰。 |
| 在代码块内部定义函数时 | 使用函数表达式(用 let 或 const),以确保块级作用域的行为一致。 |