- Published on
函数对象的实例化
在 ECMAScript 规范中,函数是头等公民,其本质是一种支持内部 [[Call]] 方法的特殊对象。一个函数从源代码文本到成为可执行的函数对象,其创建过程并非单一操作,而是遵循一套由规范定义的、严谨的实例化算法 (instantiation algorithm)。深入理解这一过程,能够从根本上揭示 JavaScript 中闭包 (Closure)、this 绑定、new 关键字以及 prototype 机制的底层原理。
核心抽象操作
函数实例化的过程依赖于两个核心的、由规范定义的抽象操作。
OrdinaryFunctionCreateOrdinaryFunctionCreate(Function.prototype, sourceText, ParameterList, Body, thisMode, env, privateEnv) 负责创建函数对象的基础结构。它会:
- 创建一个继承自
Function.prototype的新对象F。 - 为
F填充一系列内部插槽 (internal slots),这些插槽是引擎内部使用的数据,无法直接访问。其中最重要的包括:[[Environment]]: 捕获函数创建时所处的词法环境 (Lexical Environment)env,这是闭包的实现基础。[[PrivateEnvironment]]: 记录私有成员的词法环境privateEnv。[[ECMAScriptCode]]: 存储函数的参数列表ParameterList和函数体Body。[[ThisMode]]: 根据thisMode和Body是否为严格模式,设定this的绑定模式(lexical,strict, 或global)。[[Call]]: 定义该函数对象被调用时的行为。
MakeConstructorMakeConstructor(F, writablePrototype?, prototype?) 负责为一个已创建的函数对象 F 赋予作为构造函数的能力。
- 为
F添加[[Construct]]内部方法,定义其通过new关键字被调用时的行为。 - 为
F创建一个prototype属性。这个属性的值是一个普通对象,该对象上会自动包含一个constructor属性,指回函数F自身。
普通函数实例化 (InstantiateOrdinaryFunction)
一个常规的 function 声明或表达式,其实例化流程如下。
算法流程
- 捕获环境: 首先,捕获当前执行上下文的词法环境
env和私有环境privateEnv。 - 创建函数对象: 调用
OrdinaryFunctionCreate,并传入%Function.prototype%作为原型,以及函数的源码、参数、函数体、this模式(non-lexical-this)和第一步捕获的环境,生成一个基础函数对象F。 - 设置名称: 调用
SetFunctionName,根据函数的声明标识符(或推断的名称)为F设置.name属性。 - 赋予构造能力: 调用
MakeConstructor(F),为F添加[[Construct]]内部方法,并为其创建一个.prototype对象。 - 返回: 最终返回这个功能完备的函数对象
F。
MakeConstructor 的关键步骤MakeConstructor 在赋予函数构造能力时,执行了我们熟知的原型创建过程:
// 伪代码,描述 MakeConstructor 的核心行为
function MakeConstructor(F) {
// a. 为 F 添加 [[Construct]] 内部方法,使其支持 new 调用
F.[[Construct]] = internalConstructorLogic;
// b. 创建一个全新的普通对象作为 F 的 prototype
let prototype = Object.create(Object.prototype);
// c. 在这个新对象上,添加一个 constructor 属性,指回 F 自身
Object.defineProperty(prototype, 'constructor', {
value: F,
writable: true,
enumerable: false,
configurable: true
});
// d. 将这个新对象赋值给 F 的 prototype 属性
Object.defineProperty(F, 'prototype', {
value: prototype,
writable: true,
enumerable: false,
configurable: false
});
}
箭头函数实例化 (InstantiateArrowFunctionExpression)
箭头函数的实例化流程更简单,因为它被设计为不具备某些普通函数的功能。
算法流程
- 捕获环境: 与普通函数一样,捕获当前的词法环境
env和私有环境privateEnv。 - 创建函数对象: 调用
OrdinaryFunctionCreate,但thisMode参数被明确指定为lexical-this。 - 设置名称: 调用
SetFunctionName,根据其被赋值的变量名等上下文推断并设置.name属性。 - 返回: 直接返回创建的函数对象
closure。
与普通函数的根本差异
实例化过程决定了行为差异
- 无
MakeConstructor调用: 箭头函数的实例化流程完全跳过了MakeConstructor这一步。这是它没有prototype属性、不能作为构造函数(没有[[Construct]]内部方法) 的根本原因。 - 词法
this: 在调用OrdinaryFunctionCreate时,thisMode被硬编码为lexical-this。这导致其[[ThisMode]]内部插槽被设置为lexical,从而在执行时,其this值不是动态绑定的,而是从其被定义时捕获的[[Environment]]中静态解析的。 - 无
arguments绑定: 箭头函数在创建时,其内部[[ECMAScriptCode]]的解析方式也不同,规范规定不为其创建本地的arguments对象绑定。任何对arguments,super,this或new.target的引用,都必须从其捕获的词法环境env中解析。