JavaScript中的this的指向问题(包括箭头函数)
前言
了解this的指向问题,是一个前端小白的必经之路。在最近老是被this的问题所困扰,就在各种博客收集总结,然后自己来写一篇,在以后忘记的过程中,我可以拿出来看看,加深自己对this的理解。
其中可能有一定的错误,但是请你们谅解一下小白的痛苦。
普通函数
理解this的关键
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。
1、默认绑定规则
在全局下,this就是执行window
//现象一
console.log(this === window); //true
//现象二 函数的独立调用
function a(){
console.log(this === window); //true
}
a() // 等价于 window.a()
2、隐式绑定规则
谁调用this就指向谁
这里一般是对象的形式来调用函数。
每个函数执行时,内部都有一个this的存在(可能指向相同)。
//现象一
let obj = {
name: 'james',
getName: function(){
console.log(this); //obj
function b(){
console.log(this);
}
b() // window 原因: window.b()
}
}
obj.getName()
//现象二 立即执行函数
(function(){
console.log(this); //window
})()
//解释(相当于下面的函数,定义函数体之后,下步就执行)
function b(){
console.log(this);
}
b() // 原因: window.b()
//想象三
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn; //这里函数并没有被执行,所以this的指向没有确定
j(); //window.j() 这里是window调用,所以this只想问window
3、显示绑定
call / apply / bind
改变this的指向
他们的第一个参数都是对象,如果不是对象,会进行相应的转化
obj.call(1) //number
obj.call(false) //boolean
//特殊
obj.call(null) //window
obj.call(undefined) //window
4、new绑定
构造函数中的this指向实例化对象
function Person(){
this.name = 'james'
}
new Person()
//等价于
function Person(){
let this = {}
this.name = 'james'
return this
}
//返回一个this的对象,当创建实例对象的时候。this就是指向他
对于new绑定,如果return的是基本数据类型,不会改变this的指向,如果返回的是引用数据类型,就会改变this的指向。
function fn()
{
this.user = 'james';
return function(){}; //返回一个 函数、对象、数组,改变this的指向,指向返回的新的对象地址
}
var a = new fn;
console.log(a.user); //undefined
function fn()
{
this.user = 'james';
return 1; //如果返回的基本数据类型, 不会改变this的指向
}
var a = new fn;
console.log(a.user); //james
回调函数中的this默认是指向window的,因为本质上是在函数内callback,并没有.前的对象调用
优先级
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
证明显示绑定优先于隐式绑定
function foo(){
console.log(this.a);
}
let obj1 = {
a: 2,
foo
}
let obj2 = {
a: 3,
foo
}
obj1.foo() // 2
obj2.foo() // 3
//证明显示绑定优先于隐式绑定
obj1.foo.call(obj2) //3
从上面代码可以知道, 当隐式绑定调用的时候,this分别指向obj1和obj2,各自打印出2和3,对obj1.foo.call(obj2)
来说, 打印出 3 ,就说明this的指向是obj2(简单理解,就是改变了this的指向),从而就说明显示绑定优先于隐式绑定。
证明new绑定优先于显示绑定
function foo(a){
this.a = a
}
var obj = {}
//使用bind函数改变this的指向为obj,(bind()返回一个新的函数)
var bar = foo.bind(obj)
bar(2)
//执行bar(2),从而打印出obj.a = 2
console.log(obj.a); //2
//重点: new绑定优先于显示绑定
var baz = new bar(3)
console.log(obj.a); //2
console.log(baz.a); //3
最后打印的解释: var bar = foo.bind(obj)
是通过bind来绑定的,就是显示绑定,这时候的this应该是指向obj的, var baz = new bar(3)
这是通过new绑定,来创建一个实例对象,并且传递的参数为3,所以console.log(baz.a)
打印出来为3, 但这时的obj.a呢?该是多少呢? bar
是通过bind()显示绑定的,其中的this指向的是obj,,, 但是执行 new bar(3)
, 传递的参数是3,如果this还是指向obj的话,那么打印出来 console.log(obj.a) 应该是3,但是结果显示的是2,从而就说明了其中的this发生了变化,因此打印出obj.a依旧是2
, 因为其中的this指向了baz了,就不会改变obj原来的值,这也就说了new绑定的优先级高于显示绑定。
箭头函数
在ES6中的箭头函数中是没有this存在的。
箭头函数中的this指向,是父级作用域中的this,它们是相同的。(取决于父环境中的函数的this的指向,在定义时就已经确定了,跟普通的函数的this有点不一样。)
function a(){
b = 2
console.log(this); //window
var c = () => {
console.log(this); //window
}
c()
}
a()
这里的箭头函数是没有this的,从而就会从上一级找,就会找到a函数,a函数中是有this的,并且this是指向window, 从而箭头函数中的this也是指向window的。
let a = () => {
console.log(this); //window
}
a()
//这里this又会继续的往上找,那么就会找到window的顶级对象上去,从而是window
对于上面的四种改变this的指向,对于箭头函数来说,都是无效的,并且对于new绑定来说,还会报错,因为箭头函数不能作为构造函数。
面试题
function Foo() {
getName = function(){
console.log(1);
}
}
Foo.getName = function() {
console.log(2);
}
Foo.prototype.getName = function() {
console.log(3);
}
var getName = function() {
console.log(4);
}
function getName() {
console.log(5);
}
//输出
Foo.getName() // 2
getName() // 4
Foo().getName() // 1
getName() // 1
new Foo.getName() // 2
new Foo().getName() // 3
new new Foo().getName() //3
解析:
第一问: 可以看出这是构造函数的静态成员,就直接调用即可,所以打印出 2。
第二问: 这是一个全局的函数调用,由于存在预编译,function 和 var都会变量提升, 当代码执行下来,getName重新变量赋值,所以打印出 4。
第三问: 执行Foo(),函数体getName()没有是var定义,所以是一个全局函数,从而覆盖掉以前的getName函数,从而打印出1。
第四问: 全局执行getName, 所以打印出 1。
第五问: 这里开始,我们就需要理解优先级,看下面的优先级表
从图可以看出来, .
的优先级高于 new(无参数列表)
, 所以上面的表达式可以看成 new (Foo.getName)()
, 从而可以看出这是 Foo的静态成员,打印出 2。
第六问: new Foo().getName()
这里 .
和 new(带参数聊表)
是同级,所以从左到右执行,先进行实例化对象,从而在原型上找到getName(),从而打印出 3;
第七问: new new Foo().getName()
, 这里new的优先级比较低, new (new Foo().getName() )
, 从而也等于 3。
参考博客: