关于函数声明和函数表达式的分析

36 阅读2分钟

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),以确保块级作用域的行为一致。