函数声明整块提升并优先入驻AO,函数表达式和箭头函数仅变量名提升、函数体延迟执行;AO在执行上下文激活时完成参数绑定与属性填充,决定调用可行性。

要透彻掌握三种函数声明方式在执行上下文激活阶段的物理细节,关键不是死记语法差异,而是看清它们在**编译阶段如何被处理、在AO(活动对象)中如何注册、以及在执行阶段如何被调用**——这三个环节共同决定了“为什么有的能提前用,有的必须后写,有的会覆盖,有的不覆盖”。
函数声明(function foo() {}):整块提升,优先入驻AO
这是唯一一种在编译阶段就完成“函数体注册”的方式。JS引擎扫描到 function foo(a) { return a * 2; } 时,直接把整个函数对象塞进当前上下文的AO里,形参 a、函数名 foo 全部初始化完毕,值就是函数本身。
- 即使调用写在声明前(如先
foo(3)再function foo(){...}),也能成功——因为AO里已有可执行对象; - 若同一作用域重复声明,后声明覆盖前声明(AO中同名属性被重写);
- 它不依赖变量赋值操作,所以不存在“undefined → 函数”的中间状态。
函数表达式(const fn = function() {}):仅变量名提升,函数体留待执行
编译阶段只处理左侧:const fn 或 var fn 被登记进AO,初始值为 undefined(const 实际是暂时性死区,但逻辑上仍属“未初始化”);右侧的 function() {} 完全不参与编译,等到执行阶段遇到赋值语句时才创建函数对象并赋给 fn。
- 因此
fn()放在赋值前会报错(TypeError: fn is not a function); - AO里只有
fn: undefined,直到执行流走到= function...这一行; - 它本质是“变量绑定 + 函数创建”两步操作,不能拆开理解为“声明函数”。
箭头函数(const fn = () => {}):与函数表达式物理行为一致
箭头函数没有自己的 this、arguments、prototype,但它在执行上下文中的加载机制和函数表达式完全相同:编译阶段只登记变量名,函数体延迟创建。区别只在运行时行为,不在上下文激活流程。
- 同样受暂时性死区约束(
const/let声明)或变量提升约束(var声明); - AO中不会出现“箭头函数专属字段”,它就是一个普通函数对象,只是内部实现更轻量;
- 没有函数名提升,哪怕写成
const foo = () => {},foo仍是变量名,不是函数声明名。
执行上下文激活那一刻:AO如何“活”过来
当函数被调用,新执行上下文入栈,AO从“变量对象”转为“活动对象”——此时所有已注册项才真正可访问:
- 函数声明注册的
foo已是可执行函数; - 函数表达式注册的
fn此时仍是undefined,除非赋值语句已在前面执行过; - 参数按顺序绑定到AO对应属性(
arguments对象也在此刻生成); - 所有
var变量、函数声明、形参全部就位,AO结构固定,不再动态增删。
不复杂但容易忽略:所谓“提升”,不是代码被挪到顶部,而是编译阶段对AO的预填充动作;所谓“不能提前调用”,是因为AO里还没放进去可执行的东西。盯住AO这个容器的填充时机和内容,就抓住了全部物理细节。

















