关于this
当一个函数被调用时,会创建一个活动记录(有时候也成为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this 就是这个记录的一个属性。
this全面解析
调用位置
首先清楚一个概念
调用位置:调用位置就是函数在代码中被调用的位置(而不是声明位置)。
例如:
|
|
某些编程模式会隐藏真正的调用位置。最重要的是要分析调用栈(就是为了达到当前执行位置所调用的所有函数。也称为环境栈)。
|
|
首先,最后一句调用了 baz()
,所以那个位置就是baz()
的调用位置,将baz()
添加到调用栈里。然后 baz()
里面又调用了 bar()
,所以 bar()
的调用位置是在这,同样将bar()
加入到调用栈里面。接着,bar()
里面又调用了 foo()
,所以 foo()
的调用位置是在bar()
里面,将 foo()
加入到调用栈中。
明确调用位置对我们分析 this 的指向有很大的帮助。
绑定规则
默认绑定
默认绑定即独立的函数调用,当其他规则无法应用时的默认规则,如:
|
|
调用 foo()
的时候其实相当于 window.foo()
,所以 this.a 其实指向的是 window.a
隐式绑定
|
|
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文的对象。因为调用 foo()
时 this
被绑定到obj
,因此 this.a
和obj.a
是一样的
显示绑定
即使用 apply()
和 call()
方法。它们的第一个参数是一个对象,在调用函数时将其绑定到 this
。他们的主要区别就是第二个参数。
看个例子
|
|
我们创建了函数 bar()
,并在内部调用了 foo.call(obj)
,因此强制把 foo
的 this
绑定到了 obj
。之后无论如何调用 bar()
,它总会手动在 obj
上调用 foo
。这种绑定是一种强制绑定,也成为硬绑定。
由于硬绑定是一种非常常用的模式,所以 ES5 提供了 bind()
函数。用法如下
|
|
new绑定
使用 new 调用函数只是对函数的 ” 构造调用 “,所有的函数都可以使用 new 来调用。
使用 new 来调用函数时,会自动执行如下操作
- 创建(或者说构造)一个全新的对象
- 这个新对象会被执行
[[Prototype]]
连接 - 这个新对象会绑定到函数调用的
this
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象
用代码表示就是如下步骤
|
|
使用 new 来调用 foo( … ) 时,我们会构造一个新对象并把它绑定到 foo( … ) 调用中的 this
优先级
毫无疑问,默认绑定优先级最低
接下来,我们看看其他绑定的优先级
|
|
|
|
|
|
判断this
- 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象
var bar = new foo()
- 函数是否通过 call、apply(显式绑定)或者硬绑定调用(bind)?如果是的话,this 绑定的是指定的对象
var bar = foo.call()
- 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象
var bar = obj1.foo()
- 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。
var bar = foo()
箭头函数
箭头函数不适用 this 的四种标准规则,而是根据外层作用域来决定
来看下面这个例子
|
|
foo()
内部创建的箭头函数会捕获调用foo()
时的this
。由于 foo()
的 this 绑定到 obj1
,bar
的 this
也会绑定到 obj1
,箭头函数的绑定无法修改。(new 绑定也不行!)
ES6 标准入门里面对箭头函数 this 的指向有如下说法:
- 函数体内的 this 对象就是定义时所在的对象,而不是调用时所在的对象。
总结
- 箭头函数的 this 绑定看的是 this 所在的函数定义在哪个对象下,绑定到哪个对象则 this 就指向哪个对象
- 一般情况下是默认绑定,如果有 new 绑定则 new 绑定优先级最高,其次是显式绑定,然后再是隐式绑定。如果有对象嵌套的情况,则 this 绑定到最近的一层对象上