js中的this指向问题
前言
在刚开始学习js的时候,我们肯定都会对this的指向问题感到很迷惑,特别是在一些较为复杂的代码里更是被this搞的晕头转向。其实,我们只需要记住一句话:this始终指向它的调用者
好了,初步了解了this的指向问题,那么问题来了,我们又为什么要使用this呢?首先我们来看一段话:
从前有座山,
山里有座庙叫做神庙,
神庙里面有好多和尚,
和尚们都很喜欢神庙,
于是和尚们都经常挑水到神庙。
大家可以看见这段话里面出现了很多次‘神庙’,不太符合我们平时说话的习惯,太过于啰嗦,实际上我们平时说话会保留第一个名词,接下来的话里如果提到这个名词都会用‘它’或者‘这’来代替这个 名词,比如这样:
从前有座山,
山里有座庙叫做神庙,
这里面有好多和尚,
和尚们都很喜欢这,
于是和尚们都经常挑水到这儿。
这样读起来是不是舒服多了,其实我们写代码时也是这样,就比如下面这段代码:
var dog = { type: 'white', size: 'big', description: function (){ console.log( "The dog is" + dog.type + "and the size is" + dog.size
//"The dog is" + this.type + "and the size is" + this.size
)
}
}
dog.description();
我们每次都用对象的名字来调用它的属性,如果对象名字很长或者要调用的属性很多,就会使代码量增加,代码看起来也不整洁
this的四种绑定规则
1、默认绑定与隐式绑定
function person() { console.log(this); } person();//window
可以看到最后输出的是window对象,这就是默认绑定,之所以说是默认绑定,是因为person的调用不属于任何人,函数被调用的时候,this默认指向window全局对象
function person() { console.log(this.name) } var p1 = { name: 'Andy', person: person } var p2 = { name: 'Mary', person: person } p1.person();//Andy p2.person();//Mary
p1.person()和p1.person()这两种调用方式属于隐式绑定,person是作为p1和p2的方法被调用的,谁调用person,this就指向谁。
2、显示绑定
function person() { console.log(this.name) } var p1 = { name: 'Andy', } var p2 = { name: 'Mary', } person.call(p1);//Andy p1作为this person.call(p2);//Mary p2作为this
如果person是通过call、apply、bind调用的,那么这就是显示绑定,想绑定哪个方法就绑定哪个方法。
3、关键字new绑定(构造函数绑定)
function Person(name) { this.name = name; this.sayName = function (){ console.log("我的名字是" + this.name) }; } var name = "萌萌" var p = new Person("白白"); p.sayName();//白白
new关键字放在一个函数调用的前面会js编译器会做四件事,new的详细介绍可以看我的另一篇,通过new绑定之后,用‘白白’这个变量名进行对象实例化,这个函数中的this就是这个新的对象p的实例化对象‘白白’,this就与‘白白’进行了绑定,即使出现了同名的变量name也是不会有影响的
4、箭头函数
特别值得注意的是,箭头函数不绑定this,所以会无视以上所有的规则,但是它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值,和调用方式无关。接下来通过两个例子来说明这一点
function Person(){ this.age = 10; setTimeout(function () { console.log(this.age); // 输出undefined }, 1000); } var p = new Person();
function Person(){ this.age = 10; setTimeout(()=> { console.log(this.age); // 输出10 }, 1000); } var p = new Person();
在上面没有使用箭头函数的例子当中,setTimeout内部的函数是被global调用的,而global没有age这个属性,因此输出undefined。
第二个例子使用了箭头函数,this就会使用lexical scope(词法作用域)中的this,就是Person,因此输出10。
绑定优先级
如果多重绑定规格都适用,那么绑定规则的优先级顺序是这样的:
- 箭头函数
- 关键字new调
- 显式绑定
- 隐式绑定
- 默认绑定
箭头函数优先级最高,会无视2-5绑定规则。而默认绑定优先级最低,只有其他绑定都不使用的时候,才会使用默认绑定。
实例题
1、对象中的方法中的this
var o = { a: 10, b: { a: 12, fn: function(){ console.log(this.a); // 12 console.log(this); // 输出结果是 b 对象
} } } //调用 o.b.fn();
![](https://img2020.cnblogs.com/blog/2580638/202110/2580638-20211015190220864-1310936921.png)
2、改变调用方法,不直接调用:改用赋值后调用,此时this的指向为window,所以this.a的输出结果为 undefined,因为全局中没有全局变量a。
var o = { a: 10, b: { a: 12, fn: function(){ console.log(this.a); //undefined 若在对象o外定义a,则输出的就是其在外定义的值(全局变量) console.log(this); // window } } } var j = o.b.fn; //只是将b对象下的方法赋值给j,并没有调用 j(); //调用,此时绑定的对象是window,并非b对象直接调用 就是上面提到的默认绑定
3、在对象方法中调用
var point = { x : 0, y : 0, moveTo : function(x, y) { console.log(x);//1 console.log(y);//1 console.log(this.x); // 0 console.log(this.y); // 0 } }; point.moveTo(1, 1)//this 绑定到当前对象,即 point 对象
4、作为函数调用
function someFun(x) { this.x = x; } someFun(5); //函数被调用时,this绑定的是全局对象 window,相当于直接声明了一个全局变量x,并赋值为5 console.log(x); //输出5 x 已经成为一个值为 5 的全局隐式变量
5、setInterval和setTimeout定时器中的this指向全局对象
var a = 10; var oTimer1 = setInterval(function(){ var a = 20; console.log(this.a); // 10 clearInterval(oTimer1); },100);
6、apply和call中的this指向参数中的对象
var a = 10; var foo = { a: 20, fn: function(){ console.log(this.a); } }; var bar ={ a: 30 } foo.fn.apply();//10(若参数为空,默认指向全局对象) foo.fn.apply(foo);//20 foo.fn.apply(bar);//30
7、作为构造函数
function Point(x, y){ console.log(this); // point对象 (函数在这里调用时输出window) this.x = x; this.y = y; this.moveTo = function(x,y){ this.x = x; this.y = y; console.log(this.x); console.log(this.y); } }
var p1 = new Point(0,0); //注意这种形式方法的调用及apply、call的使用 var p2 = { x:0, y:0 }
p1.moveTo(1,1); //1 1 p1.moveTo.apply(p2,[10,10]);//10 10 console.log(x);// x is not defined console.log(y);//
好啦,关于this的学习就到这里了,文中部分内容转载自:https://zhuanlan.zhihu.com/p/82504422
https://www.jianshu.com/p/5f8440535a2a