子曰:this 是什么?
什么是this
this 不是编写时绑定,而是运行时绑定。它依赖于函数调用的上下文条件。this 绑定与函数声明的位置没有任何关系,而于函数被调用的方式紧密相连。
当一个函数被调用时,会建立一个称为执行环境的活动记录。这个记录包含函数是从何处(调用–call-stak)被调用的,韩式是如何被调用的,被传递了什么参数等信息。这个记录的属性之一,就是函数执行期间将被使用的this 引用。
this 实际上是在函数被调用时建立的一个绑定,它指向什么是完全由函数被调用的调用点(call-site)来决定的。
调用栈(call-stack)
使我们到达当前执行位置而被调用的所用方法的堆栈。
调用点(call-site)
- 函数在代码中被调用的位置(不是被声明的位置)。
 - 调用点位于当前执行中的函数之前的调用
 
展示调用栈和调用点:
1  | function baz () {  | 
判断调用点的四种规则
1. 默认绑定(Default Binding)
独立函数调用
1  | function foo () {  | 
上述为非严格模式,因此this 为全局对象,而在严格模式下(strict mode)下,this 为undefined
1  | function foo () {  | 
2. 隐含绑定(Implicit Binding)
调用点是否有一个环境对象(content object),也称为拥有者(owning)或容器(containing)对象。
1  | function foo () {  | 
首先foo() 被声明然后作为引用属性添加到obj。调用点使用obj 环境来 引用 函数,也就是说obj 对象在函数被调用的时间点上“拥有”或“包含” 这个 函数引用。
在foo() 被调用的位置上,它被冠以一个指向obj 的对象引用。当一个方法引用存在一个环境对象时,隐含绑定 规则会说:这个环境对象应当被用于这个函数调用的this 绑定。
因为obj 是foo() 调用的this,所以this.a 就是obj.a 的同义词。
还要注意:只有对象属性引用链的最后一层是影响调用点的。
隐含丢失
当一个隐含绑定 丢失了它的绑定,它通常会退回默认绑定,根据strict mode 的状态,其结果不是全局对象就是undefined。
1  | function foo () {  | 
尽管bar 似乎是obj.foo 的引用,但实际上它只是另一个foo 本身的引用而已。起作用的调用点是bar(),因此 默认绑定 适用于这里。
考虑传递一个回调函数:
1  | function foo () {  | 
参数传递仅仅是一种隐含的赋值,而且因为我们在传递一个函数,它是一个隐含的引用赋值,所以最终结果前一个代码段一样。
如果接受传递回调的函数是语言内建的,也是一样的。
1  | function foo () {  | 
下面的setTimeout() 实现,等同于JavaScript 环境内建的实现:
1  | function setTimeout(fn, delay) {  | 
注意:事件处理器强制回调的this 指向触发事件的DOM 元素。
3. 明确绑定(Explicit Binding)
使用函数的call(...) 和apply(..) 方法,强制一个函数调用使用某个特定对象作为this 绑定。它们接收的第一个参数都是一个用于this 的对象,之后使用这个指定的this 来调用函数。因为明确指定this 是什么,所以我们称这种方式为 明确绑定(explicit binding)。
1  | function foo () {  | 
通过foo.call(..) 使用 明确版定 来调用foo,允许我们强制函数的this 指向obj。
靠 明确绑定  不能解决函数“丢失” 自己原本的this 绑定、被第三方框架覆盖等问题。
硬绑定(Hard Binding)
1  | function foo (something) {  | 
ES5 提供Function.prototype.bind ,像这样使用:
1  | function foo (something) {  | 
bind(..) 返回一个硬编码的新函数,它使用你指定的this 环境来调用原本的函数。
API 调用的“环境”
许多库中的函数,和许多在JavaScript 语言以及宿主环境中的内建函数,都提供一个可选参数,通常称为“环境(context)”,这种设计作为一种替代方案来确保你的回调函数使用特定的this 而不必非得使用bind(..)。
1  | function foo (el) {  | 
3. new 绑定(new Binding)
new 是函数调用可以绑定this 的最后一种方式,我们称之为 new 绑定(new binding)
1  | function foo (a) {  | 
通过在前面使用new 来调用foo(..),我们构建了一个新的对象并把这个新对象作为foo(..) 调用的this。
判断this
- 函数是通过
new被调用的吗(new 绑定)?如果是,this就是新构建的对象。1
var bar = new foo()
 - 函数是通过
call或apply被调用(明确绑定),设置是隐藏在bind硬绑定之中吗?如果是,this就是那个被明确指定的对象1
var bar = foo.call(obj2)
 - 函数是通过环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,
this就是那个环境对象。1
var bar = obj1.foo()
 - 否则,使用默认的
this(默认绑定)。如果在strict mode下,就是undefined,否则是global对象。1
var bar = foo()
 
特殊规则
词法this
ES6 引入了箭头函数(arrow-function),箭头函数不是通过function 关键字声明的,而是通过所谓的“大箭头”操作符:=>。箭头函数从封闭它的(函数或全局)作用域采用this 绑定。
下面是箭头函数的词法作用域:
1  | function foo () {  | 
在foo() 中创建的箭头函数在词法上捕获foo() 被调用时的this,不管它是什么。因为foo() 被this 绑定到obj1,bar(被返回的箭头函数的一个引用)也将被this 绑定到obj1。一个箭头函数的词法绑定是不能被覆盖的(就连new 也不行)。