函数声明整块提升、可先调用后定义,函数表达式仅变量声明提升(var)或不提升(let/const),必须先定义后调用;前者只能出现在顶层或函数体,后者可嵌入任意表达式位置;前者名称全局可见,后者内部名仅限函数体内使用。

函数声明和函数表达式表面都是定义函数,但行为逻辑完全不同——关键不在“怎么写”,而在“什么时候绑定”和“能不能提前用”。
提升行为:一个整块提前,一个只留空名
函数声明在代码执行前就完成整个定义的注册,名字和函数体一起被“拎到”作用域顶部,所以可以先调用后写声明。
- function say() { console.log('hi'); }
say(); // ✅ 正常输出 - var fn = function() { console.log('bye'); };
fn(); // ❌ 报错,此时 fn 是 undefined - let fn2 = () => {};
fn2(); // ❌ ReferenceError,let 声明不提升,直接进入暂时性死区
语法位置:一个必须独立成句,一个哪儿都能塞
函数声明只能出现在顶层或函数体内,不能直接写在 if、for、try 等块级结构里(否则行为不一致,尤其严格模式下会报错)。
- ✅ 合法:
function foo() {}
function bar() { function inner() {} } - ❌ 非标准(应避免):
if (true) { function baz() {} } // 不同引擎处理不同 - ✅ 函数表达式自由得多:
if (ok) { const handler = function() { /* ... */ }; }
[1,2].map(function(x) { return x * 2; })
命名与作用域:一个名字对外可见,一个名字只对自己有用
函数声明的名字是作用域内的正式标识符,可被外部访问;函数表达式的名称(如果写了)仅用于函数内部,比如递归或调试栈显示,外部完全不可见。
- function greet() { console.log('hello'); }
greet(); // ✅
typeof greet === 'function' // ✅ - const talk = function hello() { console.log(hello); };
talk(); // ✅ 输出 hello 函数本身
hello(); // ❌ ReferenceError - talk.name // 'hello' —— name 属性保留了内部名,但没创建新绑定
使用场景:一个适合主流程,一个适合灵活嵌套
函数声明适合定义模块级、可复用的核心逻辑;函数表达式更适合回调、IIFE、动态生成、闭包封装等需要值语义的场合。
- IIFE 必须用表达式形式:
(function() { console.log('run once'); })();
+function(){}(); // 也可,靠运算符强制解析为表达式 - 对象方法、事件监听器、数组方法参数,基本都依赖函数表达式:
btn.addEventListener('click', function() { /* ... */ });
const obj = { init: function() {} }; - 箭头函数属于函数表达式变体,同样不提升、无 this、不可 new,内存创建时机与普通表达式一致











