使用 JavaScript 开发的时候,很多开发者多多少少会被 this
的指向搞蒙圈,但是实际上,关于 this
的指向,记住最核心的一句话:哪个对象调用函数,函数里面的this指向哪个对象。
下面分几种情况谈论下
1、普通函数调用
这个情况没特殊意外,就是指向全局对象-window。
var username='cn'
function fn(){
alert(this.username);//cn
}
fn();
let username='cn'
function fn(){
alert(this.username);//undefined
}
fn();
2、对象函数调用
这个相信不难理解,就是那个函数调用,this指向哪里
window.b=2222
let obj={
a:111,
fn:function(){
alert(this.a);//111
alert(this.b);//undefined
}
}
obj.fn();
// ------------------------------------------
window.b=2222
let obj={
a:111,
fn:function(){
alert(this.a);
alert(this.b);
}
}
fn2 = obj.fn;
fn2() //undefined 2222
// --------------------------------------------
let obj1={
a:222
};
let obj2={
a:111,
fn:function(){
alert(this.a);
}
}
obj1.fn=obj2.fn;
obj1.fn();//222
//
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
o.b.fn(); // 12
-------------------------------------------------
function foo() {
console.log( this.a );
}
var a = 2;
var obj = {
a: 3,
foo: foo
};
obj.foo(); // 3
如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,例子3可以证明,如果不相信,那么接下来我们继续看几个例子。
3、构造函数调用
let TestClass=function(){
this.name='111';
}
let subClass=new TestClass();
subClass.name='cn';
let subClass1=new TestClass();
console.log(subClass.name);//cn
console.log(subClass1.name)//11
TestClass.prototype.firstName = 'li'
subClass.firstName='zhang'
console.log(subClass.firstName); // zhang
TestClass.prototype.age = '18'
console.log(subClass.age); // 18
subClass1.age //18
使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4
个步骤:
(1) 创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) ;
(3) 执行构造函数中的代码(为这个新对象添加属性) ;
(4) 返回新对象。
4:apply和call调用
apply和call简单来说就是会改变传入函数的this。
let obj1={
a:222
};
let obj2={
a:111,
fn:function(){
alert(this.a);
}
}
obj2.fn.call(obj1);
此时虽然是 obj2
调用方法,但是使用 了call
,动态的把 this
指向到 obj1
。相当于这个 obj2.fn
这个执行环境是 obj1
。apply
和 call
详细内容在下面提及。
5、箭头函数调用
首先不得不说,ES6 提供了箭头函数,增加了我们的开发效率,但是在箭头函数里面,没有 this
,箭头函数里面的 this
是继承外面的环境。
let obj={
a:222,
fn:function(){
setTimeout(function(){console.log(this.a)})
}
};
obj.fn();
不难发现,虽然 fn() 里面的 this 是指向 obj ,但是,传给 setTimeout 的是普通函数, this 指向是 window , window 下面没有 a ,所以这里输出 undefined。
换成箭头函数
let obj={
a:222,
fn:function(){
setTimeout(()=>{console.log(this.a)});
}
};
obj.fn(); // 222
这次输出 222 是因为,传给 setTimeout 的是箭头函数,然后箭头函数里面没有 this ,所以要向上层作用域查找,在这个例子上, setTimeout 的上层作用域是 fn。而 fn 里面的 this 指向 obj ,所以 setTimeout 里面的箭头函数的 this ,指向 obj 。所以输出 222 。
5、隐式丢失(函数别名/回调函数)
function foo() {
console.log( this.a );
}
var a = 2;
var obj = {
a: 3,
foo: foo
};
var bar = obj.foo;
bar(); // 2
obj.foo 是引用属性,赋值给bar的实际上就是foo函数(即:bar指向foo本身)。
那么,实际的调用关系是:通过bar找到foo函数,进行调用。整个调用过程并没有obj的参数,所以是默认绑定,全局属性a。
function foo() {
console.log( this.a );
}
var a = 2;
var obj = {
a: 3,
foo: foo
};
setTimeout( obj.foo, 100 ); // 2
setTimeout( obj.foo(), 100 ); // 3
同样的道理,虽然参传是obj.foo
,因为是引用关系,所以传参实际上传的就是foo对象本身的引用。对于setTimeout
的调用,还是 setTimeout -> 获取参数中foo的引用参数 -> 执行 foo 函数,中间没有obj的参与。这里依旧进行的是默认绑定。而下面的直接运行了,而且 3提前输出,2之后输出
7、函数回调
- 普通函数
function foo(){ return function(){ console.log( this.a ); } } var a = 2; var obj = { a: 3, foo: foo }; var bar = obj.foo(); bar(); //2 var bar2 = obj.foo; bar2() //ƒ (){console.log( this.a );}
function fn() { this.user = '追梦子'; return {}; } var a = new fn; console.log(a.user); //undefined function fn() { this.user = '追梦子'; return function(){}; } var a = new fn; console.log(a.user); //undefined function fn() { this.user = '追梦子'; return 1; } var a = new fn; console.log(a.user); //追梦子 function fn() { this.user = '追梦子'; return undefined; } var a = new fn; console.log(a.user); //追梦子
如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。