当遇到疑难问题时,最好的办法就是去看官方的规范,于是,为了彻底探究 JS 中的 this 绑定规则, 我打开了ECMAScript 5.1 规范文档。。。。
规范中,关于 this
绑定,有如下几处提到,第一处是 4.3.27 节中,这个比较好理解,也就是我们都知道的,当一个函数被作为一个对象的方法调用,则函数内的 this
指向那个对象。
对象的方法
4.3.27 方法 (method)
作为属性值的函数。
注:当一个函数被作为一个对象的方法调用,此对象将作为 this 值传递给函数。
另一处来自 10.4 节:建立执行环境
其中,在 10.4.1.1 提到,初始化全局环境时,this
绑定设置为全局对象,在浏览器里就是 window
全局环境下
10.4.1.1 初始化全局执行环境
…
将this
绑定设置为 全局对象 。
而在 10.4.3 节,则提到了进入函数时的 this
绑定规则
函数内
10.4.3 进入函数代码
当控制流根据一个函数对象 F、调用者提供的
thisArg
以及调用者提供的argumentList
,进入 函数代码 的执行环境时,执行以下步骤:
- 如果 函数代码 是 严格模式下的代码 ,设
this
绑定为thisArg
。- 否则如果
thisArg
是null
或undefined
,则设this
绑定为 全局对象 。- 否则如果
Type(thisArg)
的结果不为Object
,则设this
绑定为ToObject(thisArg)
。- 否则设
this
绑定为thisArg
。
看起来有些乱,用带缩进的 if-else 伪代码重写一下吧
if(是 严格模式) {
this = thisArg
} else if(thisArg === null || thisArg === undefined) {
this = window
} else if(typeof thisArg != 'object') {
this = Object(thisArg)
} else {
this = thisArg
}
thisArg
是啥?在该规范中,代表函数的 apply
,call
,bind
等函数的设置 this 绑定的参数:
Function.prototype.apply (thisArg, argArray)
Function.prototype.call (thisArg [, arg1 [ , arg2, … ]] )
Function.prototype.bind (thisArg [, arg1 [, arg2, …]])
Array.prototype.every ( callbackfn [ , thisArg ] )
Array.prototype.some ( callbackfn [ , thisArg ] )
Array.prototype.forEach ( callbackfn [ , thisArg ] )
Array.prototype.map ( callbackfn [ , thisArg ] )
Array.prototype.filter ( callbackfn [ , thisArg ] )
好了,再来看那段绑定规则,第一条
如果 函数代码 是 严格模式下的代码 ,设
this
绑定为thisArg
。
严格模式
也就是说,在严格模式下,this
只能为 thisArg
,而当 thisArg
为 undefined
时,this
就是 undefined
,而不是 window
。
非严格模式
然后是非严格模式下:
如果 thisArg
是 null
(如 fun.call(null)
) 或 undefined
(直接调用函数),则 this
为全局对象,浏览器里就是 window
。
否则,如果 传入了 thisArg
, 但不是个对象,则把它转为对象,并赋给 this
,比如,当 fun.call('hhh')
时,打印 fun
内的 this
为
String {0: "h", 1: "h", 2: "h", length: 3, [[PrimitiveValue]]: "hhh"}0: "h"1: "h"2: "h"length: 3__proto__: String[[PrimitiveValue]]: "hhh"
否则 ,也就是仅剩的一种情况,显式的传入了一个对象作为 thisArg
参数的情况下,设 this
绑定为 thisArg
。
new 操作
13.2.2[[Construct]]
当以一个可能的空的参数列表调用函数对象 F 的 [[Construct]] 内部方法,采用以下步骤:
- 令 obj 为新创建的 ECMAScript 原生对象。
- 依照 8.12 设定 obj 的所有内部方法。
- 设定 obj 的 [[Class]] 内部方法为 “Object”。
- 设定 obj 的 [[Extensible]] 内部方法为 true。
- 令 proto 为以参数 “prototype” 调用 F 的 [[Get]] 内部属性的值。
- 如果 Type(proto) 是 Object,设定 obj 的 [[Prototype]] 内部属性为 proto。
- 如果 Type(proto) 不是 Object,设定 obj 的 [[Prototype]] 内部属性为 15.2.4 描述的标准内置的 Object 的 prototype 对象。
- 以 obj 为 this 值,调用 [[Construct]] 的参数列表为 args,调用 F 的 [[Call]] 内部属性,令 result 为调用结果。
- 如果 Type(result) 是 Object,则返回 result。
- 返回 obj
new
操作中,this
被设置为新创建的那个对象。
eval
在 10.4.2 节还提到了进入 eval
代码的 this
规则,可以看到,和前面介绍的基本没有差别,基本相当于一段普通的 JS 代码。
10.4.2 进入 eval 代码
当控制流进入
eval
代码 的执行环境时,执行以下步骤:
- 如果没有调用环境,或者
eval
代码 并非通过直接调用(15.1.2.1.1)eval
函数进行评估的,则
- 按(10.4.1.1)描述的初始化全局执行环境的方案,以
eval
代码 作为 C 来初始化执行环境。- 否则
- 将
this
绑定设置为当前执行环境下的this
绑定。- 将词法环境设置为当前执行环境下的 词法环境 。
- 将变量环境设置为当前执行环境下的变量环境。
箭头函数
ES6 中有个箭头函数,我再研究研究~~~