this的由来
javascript允许在函数体内,引用当前环境的其他变量
var fn=function(){
console.log(x)
}
上面代码使用变量x,而变量x由当前的运行环境提供。
那么问题来了,函数可以在不同的运行环境下执行,所以需要一种机制,可以在函数体内内部获取当前的运行环境(context),所以this就出现了,它的作用就是在函数体内部,指代当前的运行环境。
this的几种模式
在我理解下,this分为一下几种情况:
- 方法调用模式下,this总是指向调用它所在方法的对象,this的指向与所在方法的调用位置有关,而与方法的声明位置无关(箭头函数特殊)。
- 函数调用下,this指向window,调用方法没有明确对象的时候,this指向window,,如setTimeout,匿名函数等。
- 构造函数调用模式下,this指向被构造的对象
- apply,call,bind调用模式下,this指向第一个参数。
- 箭头函数,在声明的时候绑定this,而非取决于调用位置,换句话说就是指向它的上一层。
- 严格模式下,如果this没有被执行环境(execution context)定义,那this是为undefined。
下面我们针对这几种情况,举例说明,并说明原理。
方法调用模式
//声明位置
var fn=function(){
console.log(this.x)
}
var x="2"
var obj={
x:"1",
fn:fn
}
//调用位置
obj.fn();//1
//调用位置
fn();//2
以上代码,可以看到,this指向调用它所在方法的对象,say方法在obj对象下,所以this指向obj,fn在window对象下,所以this指向window,也可以看出来,this和声明位置无关,和调用位置有关。
函数调用模式
var x="2"
//声明位置
var fn=function(){
console.log(this.x)
}
//调用位置
fn();//2
匿名函数,setTimeout:
var that=this;
(function(){
console.log(this===that)//true
})()
setTimeout(() => {
console.log(this===that)//true
}, 0);
可以看出以上所有情况,this指向window
call,apply,bind模式下
var obj={
name:'hty',
getName:function(){
console.log(this.name)
}
}
var otherObj={
name:'hml'
}
var name='upupup'
obj.getName()//hty
obj.getName.call();//upupup
obj.getName.call(otherObj);//hml
obj.getName.apply();//upupup
obj.getName.apply(otherObj);//hml
obj.getName.bind(this)();//upupup
obj.getName.bind(otherObj)();//hml
可以看出,call,apply,bind的this指向第一个参数,如果有朋友不太明白,可以看下,我上篇写的call,apply,bind的原理就明白了。
构造函数模式下
var flag=undefined;
function Fn(){
flag=this;
}
var obj=new Fn()
console.log(flag==obj)
其实,这个this指向obj,内部原理还是用apply把this指向obj的,在后续文章,我会写一个关于new的原理,大家有兴趣,可以关注下。
严格模式
"use strict";
var fn=function(){
return this
}
fn() ==undefined;//true
可以看出,在严格模式下,fn是被直接调用的,并没有没有被执行环境所定义,也就是说不是作为对象的属性或方法调用的(如window.fn())
箭头函数
//声明位置
var fn = (()=>{
console.log(this.x)
})
var x="2"
var obj={
x:"1",
fn:fn
}
//调用位置
obj.fn();//2
//调用位置
fn();//2
以上可以看出,箭头函数在定义时就绑定了this,而非取决与调用位置了,同样用call,apply,bind都无法更改this。
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
var obj = {foo: foo};
// 尝试使用call来设定this
console.log(foo.call(obj) === globalObject); // true
// 尝试使用bind来设定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true