this 指向问题

一、常见错误理解#
- this是指向自身吗?
特别是在函数中使用 this 的时候,this 是指的所在的这个函数对象吗?看下面示例:
输出的是空字符串,因为此时 this 指向的是 window ,所以 this 并不是指向自身
- this指向函数的作用域吗?
执行后发现输出的仍然是空字符串,原因和上面一样,this 没有指向当前函数的作用域
其实 this 是在运行时绑定的,并不是在声明时绑定,this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。确定 this 指向的步骤应该是 “ 确定调用位置—应用规则—确定this指向 ”
二、调用位置#
调用位置就是函数在代码中被调用的位置(而不是声明的位置)
关键:分析调用栈,即为了到达当前执行位置所调用的所有函数。而我们关心的调用位置就在当前正在执行的函数的前一个调用中
三、绑定规则#
确定调用位置后,需要对应 this 的绑定规则,有四种绑定规则,判断条件如下:
默认绑定#
一般可以理解为无法应用其他规则时的兜底默认规则,一般适用于独立函数调用时
上面这种方式便是默认绑定,test() 不在任何对象内的独立调用,适用于默认绑定,默认绑定 this 指向的全局对象,在浏览器里面就是 window,在 node 里面就是 global
注意:在严格模式下,全局对象无法使用默认绑定,默认绑定会绑定到 undefined 上,即 this 指向 undefined
隐式绑定#
隐式绑定存在于在调用位置有上下文对象或者说调用时被对象包含或拥有
看上面函数 say 的调用,不是 say 单独调用,而是被对象 obj 包含着调用,此时 this 是指向 obj 对象的
当函数被多个对象包含时,指向它的上一级
隐式丢失#
有一种情况是看似应该是隐式绑定,但实际却是默认绑定
这种情况是不满足隐式函数绑定的,因为隐式函数绑定应当是被包含在对象中调用的,而不是说只要是对象的其中一部分就可以了,重点在于调用时是否被函数包含着!
解析:
第一个是把 obj 里的函数 say 的引用赋值给 copy 变量,再通过 copy 来调用,copy 调用时并没有被 obj 包含着调用,这就适用默认绑定规则—独立函数调用,因此此时 this 是指向 window 的
第二个例子同理,只不过看起来是调用的 obj.say(),但实际过程是:
显示绑定#
方法:使用 call 、apply 或 bind 直接指定 this 的绑定对象
我们通过使用 call() 方法,传入我们想绑定的对象,将 this 绑定到该对象上
有时候当你传入的是一个简单数据类型,而非一个对象,这时候 call 方法会自动对其进行转换,转换成该数值的对象形式,然后进行绑定,这个过程称之为 “ 自动装箱 ”
缺点:同样会出现隐式丢失的问题
解决方法:
- 硬绑定
缺点:会大大降低函数的灵活性,使用绑定之后就无法使用隐式绑定或者显式绑定来修改 this
foo.call(obj) 强制把 this 绑定到了 obj ,之后调用函数 bar ,它总会在 obj 上调用 foo ,这是显式的强制绑定,叫做硬绑定
- API调用上下文
第三方库的许多函数,以及 JS 语言和环境中许多新的内置函数,都提供了一个可选的参数,通常被称为 “ 上下文 ”,其作用和 bind() 一样,确保你的回调函数使用指定的 this
new绑定#
通过构建函数 new 关键字生成一个实例对象,此时 this 指向这个实例对象
另外,new 过程遇到 return 一个对象,此时 this 指向为返回的对象
如果返回一个简单类型的时候,则 this 指向实例对象
四、优先级#
隐式绑定与显式绑定#
结论:显式绑定 > 隐式绑定
new绑定与隐式绑定#
结论:new 绑定 > 隐式绑定
new绑定与显式绑定#
new 和 call/apply 无法一起使用,因此无法通过 new foo.call(obj1) 来直接测试,但我们可以使用硬绑定来测试
这里 bar 被硬绑定在了 obj1 上,但 new bar(3) 并没有把 obj1.a 修改为 3 。相反,new 修改了 bar() 中的 this 。因为使用了 new 绑定,我们得到了一个名为 baz 的新对象,并且 baz.a 的值为 3
结论:new 绑定 > 硬绑定(显式绑定)
所以,new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
五、箭头函数#
箭头函数的this指向规则:#
箭头函数没有 prototype (原型),所以箭头函数本身没有 this#
箭头函数的 this 指向在定义的时候继承自外层第一个普通函数的 this#
可以得出两点:
- 箭头函数的
this指向定义时所在的外层第一个普通函数,跟使用位置没有关系 - 被继承的普通函数的
this指向改变,箭头函数的this指向会跟着改变
不能直接修改箭头函数的this指向#
上个例子中的 foo 函数修改一下,尝试直接修改箭头函数的 this 指向
很明显,call 显示绑定 this 指向失败了,包括 aaply 、bind 都一样,不过它们( call 、aaply 、bind )会默认忽略第一个参数,但是可以正常传参
可以通过间接的形式来修改箭头函数的指向:去修改被继承的普通函数的this指向,然后箭头函数的this指向也会跟着改变
箭头函数外层没有普通函数,严格模式和非严格模式下它的 this 都会指向 window(全局对象)#
下面来看几个例子: