本文是在读完 es5.1 规范前14章后对于this的总结,不过更像是对整个函数调用过程的整理,懒得改了,看之前最好对于规范引用类型、词法环境等名词有一定的了解。
在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。下面是完整过程:
函数调用
产生式CallExpression : MemberExpression Arguments
如下执行:
- 令 ref 为解释执行 MemberExpression 的结果。
- 令 func 为 GetValue(ref)。(得到函数的引用)
- 令 argList 为解释执行 Arguments 的结果,产生参数值们的内部列表。
- 如果 Type(func) is not Object,抛出一个 TypeError 异常。
- 如果 IsCallable(func) is false,抛出一个 TypeError 异常。
- 如果 Type(ref) 为 Reference,那么如果IsPropertyReference(ref) 为 true,那么令 thisValue 为 GetBase(ref)。否则,ref 的基值是一个环境记录项令 thisValue 为调用 GetBase(ref) 的 ImplicitThisValue 具体方法的结果
- 否则,假如 Type(ref) 不是 Reference。令 thisValue 为 undefined。
- 返回调用 func 的 [[Call]] 内置方法的结果,传入 thisValue 作为 this 值和列表 argList 作为参数列表。
这是函数调用产生式真正的执行过程,它确定了 this 和 argments,然后传入函数的 [[Call]] 进行调用,[[Call]] 类似于我们平时使用的 Function.prototype.call
,我猜测就是Function.prototype.call
的内部实现。
然后我们来看看 this 在上面的过程是如何确定的,分两类情况:ref是规范内部引用对象或者不是内部引用对象。
规范内部引用对象,由三部分组成,基(base)值,引用名称(referenced name)和布尔值严格引用 (strict reference) 标志。通过 getValue 方法可以得到基值上的对应引用名称的真实值。
如果是引用对象,并且 base 是一个对象,那么 this 就是这个 base,如果 base 是一个环境记录项,那么 this 就是环境记录的ImplicitThisValue 方法的返回值。
ImplicitThisValue 方法在 with 以外的时候都是 undefined。
如果不是引用对象,那么 this 就是 undefined。
其实到这里 this 已经确定了,但是我还想看看后面发生了什么?
[[call]]
当用一个 this 值,一个参数列表调用函数对象 F 的 [[Call]] 内部方法,采用以下步骤:
- 用 F 的 [[FormalParameters]] 内部属性值,参数列表 args, this 值来建立函数代码的一个新执行环境,令 funcCtx 为其结果。
- 令 result 为 FunctionBody(也就是 F 的 [[Code]] 内部属性)解释执行的结果。如果 F 没有 [[Code]] 内部属性或其值是空的 FunctionBody,则 result 是 (normal, undefined, empty)。
- 退出 funcCtx 执行环境,恢复到之前的执行环境。
- 如果 result.type 是 throw 则抛出 result.value。
- 如果 result.type 是 return 则返回 result.value。
- 否则 result.type 必定是 normal。返回 undefined。
函数执行环境的建立
从 [[call]] 的执行过程可以看到,在真正执行函数代码前,要建立执行环境,也就是网上很多博客所说的预编译,个人认为用预编译来称呼不是很合适,这应该是和执行阶段串联起来的一个过程。
当控制流根据一个函数对象 F、调用者提供的 thisArg 以及调用者提供的 argumentList,进入函数代码的执行环境时,执行以下步骤:
- 如果函数代码是严格模式下的代码,设 this 绑定为 thisArg。
- 否则如果 thisArg 是 null 或 undefined,则设 this 绑定为 全局对象 。
- 否则如果 Type(thisArg) 的结果不为 Object,则设 this 绑定为 ToObject(thisArg)。
- 否则设 this 绑定为 thisArg。
- 以 F 的 [[Scope]] 内部属性为参数调用 NewDeclarativeEnvironment,并令 localEnv 为调用的结果。
- 设词法环境为 localEnv。
- 设变量环境为 localEnv。
- 令 code 为 F 的 [[Code]] 内部属性的值。
- 使用函数代码 code 和 argumentList 执行定义绑定初始化。
看了上面的过程,知道为什么 this 会从 undefined 变成全局变量了吧!也知道为什么严格模式下,是如何做到不变的全局变量的了吧!
初始化绑定
这个过程很多,简单的描述就是如下顺序:
- 参数绑定及初始化;
- 函数声明绑定及初始化;
- 变量声明绑定。
具体过程很复杂,可以通过传送门看。
小结
函数调用(确定this和arguments) → 调用内部函数 [[call]] → [[call]]在执行代码前创建函数执行上下文。
this在函数调用阶段确定,确定规则见上文。