虽然this在JavaScript中是一个老生常谈的问题,但实际场景中还是很容易出错。主要原因是this对象在JavaScript中和其他语言相比表现不太一致,而且在不同的运行环境中表现也不一致。今天我们就把浏览器中的this对象彻底聊明白,记清楚。
本文我们只讨论JavaScript在浏览器、非严格模式下this表现,在其他执行环境(比如Node.js)this的表现又不太一样了。
我们分几种情况来看
function f(){
console.log(this === window) // 打印:true
}
f();
普通的函数调用this就是window对象,我们可以这样理解 f() 就等价于window.f()。
我们修改一下上面的例子
function f(){
console.log(this === window) // 打印:true
}
function f1(){
f()
}
f1()
此时仍然打印true,因为虽然f方法在f1方法中调用,但是调用f()方法的前面仍然是省略的Window对象,所以this还是Window。
function f(){
console.log(this) // 打印:{fun: ƒ}
}
obj={
fun: f
}
obj.fun();
此时this输出的是obj对象,即调用f方法的对象。
通过以上两种情况你肯定感觉自己已经完全理解this对象了。那我们把上面的例子稍微改造一下。
function f(){
console.log(this) // 打印:Window对象
}
obj={
fun: f
}
let objf = obj.fun;
objf();
此时this打印出来又成了Window对象,是不是让你大跌眼镜。我们可以这样去理解,objf()就等价于window.objf(),调用上下文自然就变成了Window, 和前两个例子的表现并不冲突。this要看最终调用方法的对象。
let obj = {
f : function(){
console.log(this)
}
}
let obj1 = Object.create(obj);
obj1.s = 100;
obj1.f(); // 打印obj1对象
原型链中,this是调用方法的对象,和前面几个例子的表现一致。
let obj = {
s: 200,
f : function(){
console.log(this)
}
}
let obj1 = {
obj: obj,
s: 100
}
obj1.obj.f() // 打印obj对象
通过打印结果可以看出,this是obj对象。「存在多层调用时,this是最靠近f()方法的对象」 。
function f(){
console.log(this) // 打印:f方法对象
}
let obj=new f();
使用new实例化对象时,this代表被实例化的对象。
function f(){
console.log(this) // 打印:f方法对象
}
f(); // 打印Window对象
let obj={a:10};
let r = f.bind(obj);
f(); // 打印Window对象
r(); // 打印obj对象
f.apply(obj,[]); // 打印obj对象
bind方法会返回一个和f方法有相同函数体和作用域的函数,同时this对象永久的绑定在obj对象上。不管任何调用方式,this都是obj对象。但是这并不会对f方法本身产生影响,所以f()方法直接调用时,还是返回了Window对象。
apply方法也有改变执行上下文的作用,apply的第一个参数就是函数的执行上下文,所以打印了obj对象。call方法和apply雷同就不赘述了。
箭头函数的作用非常简单,就是让箭头函数内的this对象和箭头函数外面的上下文保持一致。
上面场景看起来很多,其实总结起来很简单。「不显式指定调用对象时,相当于调用省略了window,this都是window对象;当显式指定调用对象时,this是最靠近调用函数的对象」。当然对于bind、apply、call方法单独记忆就好了,因为这三个函数本身就是为了改变this指向。箭头函数就是为了不改变this指向。