从 ECMAScript 规范来看 JS 的 this 绑定规则

来源:互联网 发布:软件架构设计实例 编辑:程序博客网 时间:2024/05/16 07:59

当遇到疑难问题时,最好的办法就是去看官方的规范,于是,为了彻底探究 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,进入 函数代码 的执行环境时,执行以下步骤:

  1. 如果 函数代码 是 严格模式下的代码 ,设 this 绑定为 thisArg
  2. 否则如果 thisArgnullundefined,则设 this 绑定为 全局对象 。
  3. 否则如果 Type(thisArg) 的结果不为 Object,则设this 绑定为 ToObject(thisArg)
  4. 否则设 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 是啥?在该规范中,代表函数的 applycallbind 等函数的设置 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,而当 thisArgundefined 时,this 就是 undefined ,而不是 window

非严格模式

然后是非严格模式下:

如果 thisArgnull(如 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]] 内部方法,采用以下步骤:

  1. 令 obj 为新创建的 ECMAScript 原生对象。
  2. 依照 8.12 设定 obj 的所有内部方法。
  3. 设定 obj 的 [[Class]] 内部方法为 “Object”。
  4. 设定 obj 的 [[Extensible]] 内部方法为 true。
  5. 令 proto 为以参数 “prototype” 调用 F 的 [[Get]] 内部属性的值。
  6. 如果 Type(proto) 是 Object,设定 obj 的 [[Prototype]] 内部属性为 proto。
  7. 如果 Type(proto) 不是 Object,设定 obj 的 [[Prototype]] 内部属性为 15.2.4 描述的标准内置的 Object 的 prototype 对象。
  8. 以 obj 为 this 值,调用 [[Construct]] 的参数列表为 args,调用 F 的 [[Call]] 内部属性,令 result 为调用结果。
  9. 如果 Type(result) 是 Object,则返回 result。
  10. 返回 obj

new 操作中,this 被设置为新创建的那个对象。

eval

在 10.4.2 节还提到了进入 eval 代码的 this 规则,可以看到,和前面介绍的基本没有差别,基本相当于一段普通的 JS 代码。

10.4.2 进入 eval 代码

当控制流进入 eval 代码 的执行环境时,执行以下步骤:

  1. 如果没有调用环境,或者 eval 代码 并非通过直接调用(15.1.2.1.1)eval 函数进行评估的,则
    1. 按(10.4.1.1)描述的初始化全局执行环境的方案,以 eval 代码 作为 C 来初始化执行环境。
  2. 否则
    1. this 绑定设置为当前执行环境下的 this 绑定。
    2. 将词法环境设置为当前执行环境下的 词法环境 。
    3. 将变量环境设置为当前执行环境下的变量环境。

箭头函数

ES6 中有个箭头函数,我再研究研究~~~

原创粉丝点击