浅谈一下this
this
这个东西相信对于很多新人来说是个糊涂账,甚至老手来讲,在一些稀奇古怪的情况下都不一定完完全全的讲清楚.特别是面对脑经急转弯一样的面试题,让人苦笑不得.
一句话总结
this
既不指向函数自身,也不指向函数的词法作用域.this
实际上是在函数被调用时发生的绑定,它的指向只取决于函数被谁调用了或者函数在哪里被调用.
5大情况
因为let声明的变量在浏览器中不是顶层全局的,所以我的所有声明都用var
- 1 :
this
在全局环境下指向window (默认绑定)
在全局环境下,我们写的所有的变量和函数都是window对象里面的属性和方法.
在浏览器中打印以上代码,得到这些结果.
- 2 :
this
函数被谁调用,this
在哪里被调用.(隐式绑定)
这句好像不太好理解,我贴一下代码,然后逐句解释一下.
var a ={ //声明一个a对象,里面有个isThis方法
isThis(){
console.log(this)
}
}
a.isThis()//调用一下这个方法
结果如下:
现在理解一下这句话,this
被谁调用, a对象里面有一个函数isThis, a调用isThis, isThis被a调用, 故this指向了a对象.
我们再来看第一条:window对象中有个d方法,所以d方法被window调用的时候,this自然指向了window对象.
this
函数在哪里被调用 this
便指向谁
var a = 'a'//全局声明一个变量a
function foo(){
return this.a
}
var b = {//全局声明一个变量b
a:'this is b.a',
log(){
console.log(this.foo())
}
}
b.foo = foo
将foo方法赋给b
b.log()//调用log方法
foo()//全局调用log方法
打印结果:
首先因为foo赋值给了b,故在b调用自己对象中的方法时,this指向b自身,所以foo函数中的this指向b,在b.log()的时候,log函数是b本身的方法且被b调用,故this指向b,且执行了this.foo,foo被b调用,或者说foo在b中调用,故this指向b,log
而在全局中调用foo 因为是window调用的b(b在window中被调用)所以this指向window
再看一个例子:
function bar(){
console.log(this.a)
}
var obj1 ={
a:1,
bar
}
var obj2 = {
a:2,
obj1
}
obj2.obj1.bar()//
打印结果:1
在以上的代码中,bar函数被obj1调用,与obj2无关,所以this指向obj1,所以打印出来了.
继续理解!!
以下方代码为例:
var a ={ // 声明一个a对象,包含一个isThis方法
isThis(){
console.log(this)
}
}
a.isThis() //a调用此方法,打印出this是a自己
window.isThis = a.isThis // 将a中的isThis方法赋给window对象中
isThis()//在全局中调用该方法
实质上等于 window.isThis()
看一下结果:
在以上操作中,我们将a中的isThis方法赋给window对象中,再用window调用,this便指向了window对象.
再看以下代码:
首先我声明了一个b对象,再将a中的isThis方法赋给b,再用b去调用该方法,即打印出来了b对象自己.
我们无论是把isThis方法赋给b还是window对象,对于b对象和window对象而言,他们之中都有了一个isThis方法,再结合这一句 this
函数在哪里被调用 this
便指向谁.是不是感觉很清晰.
- 3
call
apply
中的this(显式绑定)
call和apply都接受两个参数,第一个参数是当该函数运行的时候的this环境(thisObj),第二个参数是函数接收的参数,call接受单个/多个参数,apply只能接受数组参数.
忽略第二个参数,我们单说第一个参数.
代码如下:
var a ={ //声明一个a变量
a:'this is a',
isThis(){
console.log(this)
}
}
var b = {//声明一个b变量
b:'this is b'
}
var c = {//声明一个c变量
c:'this is c'
}
function isThis(){
console.log(this)
}
a.isThis() //调用一下
isThis()
isThis.call(b)
isThis.apply(c)
a.isThis.call(b)//call一下b
a.isThis.apply(c)//apply 一下c
看一下打印结果:
在以上代码中,无论是call还是apply,无论是在哪里调用,或者被谁调用, 接受的第一个参数(thisObj),就是函数中this指向的对象,或者说第一个参数就是这个函数运行时候的this
所以,当一个函数以call或者apply方式调用的时候,call或者apply接收的第一个参数,就是函数运行时候的this
这便是显式绑定
- 4
bind
中的this(显式绑定)
现在我们认识一下bind函数.
bind
接收两个参数,与call apply很相似。第一个参数是当该函数运行的时候的this环境(thisObj),第二个参数是函数接收的参数,但是不同的是的,当一个函数调用了 call apply 方法返回的该函数被call apply之后的结果。即:
function bar(){
return this.a
}
var baz= {
a:3349
}
bar.call(baz)
返回结果是 3349
而bind 不一样,当一个函数调用了bind方法,并传入了参数返回的一个新的函数,并且该函数的this被绑定了,谁都改变不了。同样我们不讨论第二个参数的意义,我们只研究this的问题
function bar(){
return this.a
};
var a = 0;
var b = {
a:1
};
var c = {
a:2
};
var d = {
a:3
};
bar()
bar.call(b)
bar.apply(c)
结果显而易见。
那么我们现在要做这样的操作,刚才已经说过了,bind会返回一个新的函数。
所以,在下列代码中,baz赋值为bar通过bind(d)之后返回的函数
再调用一下bar,我们来看看结果
function bar(){
return this.a
};
var a = 0;
var b = {
a:1
};
var c = {
a:2
};
var d = {
a:3
};
var baz = bar.bind(d);
baz()
结果为3 ,显然,在baz这个函数被调用的时候,函数里面的this指向了d,下面一组代码将展示什么叫做硬绑定。
function bar(){
return this.a
};
var a = 0;
var b = {
a:1
};
var c = {
a:2
};
var d = {
a:3
};
var baz = bar.bind(d);
baz.call(a);
baz.call(b);
baz.call(c);
baz()
var e = {
baz
};
e.baz();
这便是硬绑定,baz函数是bar函数通过bind返回的新函数,而bar函数进行了绑定对象d的操作,所以返回的baz函数始终指向d。
换言之,一个bar函数通过调用bind绑定了一个d对象,bar.bind(d)这个调用将会返回一个新函数baz,而新函数baz的this始终都指向d这个对象,无论使用call还是apply,还是被其他对象调用或全局调用,this都不会发生改变。
- 5
new
优先级最高的this
关于js是否有类这个概念,各有说辞,有的说有,有的说没有,我比较认同一个观点是js中确实没有类,但是有类的概念,包括es6的class也不是真的类,只是语法糖,同样说道构造函数,我比较认同《你不知道的javaScript》上卷中的一段话。
“在js中,构造函数只是一些使用new操作符时被调用的函数,他们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已”
言归正传,我们今天讨论的是this的指向。
一下面代码为例:
function bar(a){
this.a = a
}
var baz = new bar(2)
console.log(baz,baz.a)
以上操作:
1,创造一个全新的对象(也可以认为是实例)
2,这个新对象会被执行到原型链上,即:baz.__proto__ === bar.prototype
3,这个新对象(实例)的this会指向他自己,而不再指向函数的this。
最后一句话是不是很难理解。
我们看看一下代码。
function bar(){
this.a = 2
}
var foo ={
bar
}
var baz = new foo.bar()
foo.bar()
第一个操作中我们是foo调用了bar,但是结果并不是我们预期所想的,foo这个对象会因为调用了bar方法而向自己赋值了a=2这个属性,显然,在被new调用的时候,bar方法里面的this指向了新创建的对象baz,并给baz复制了a=2这个属性,在第二个操作中,bar函数的this才指向了foo,并且给foo赋值a=2这个属性。
其实在我们第一段的代码中,全局调用了var baz = new bar(2)
bar函数中的this也没有指向window 而是指向了心创建的对象baz
所以可得new的优先级是高于 上文提出的状况1和状况2的
因为new和call/apply无法一起使用,我们直接比较new和bind之间的优先级,只要比bind还高,那同样就比call/apply高
function bar(a){
this.a = a
}
var a = {
bar
}
var b = {
}
var c = a.bar.bind(b,'a')
c()
以上结果很符合bind的直觉对不对,那么下来我们继续操作
function bar(a){
this.a = a
}
var a = {
bar
}
var b = {
}
var c = a.bar.bind(b,'a')
var d = new c()
在上面的操作中,我们通过new操作符,调用了a对象的bar函数通过绑定(bind) b对象后返回的c方法,可是c方法并没有像我们之前直接调用c方法那样,this指向了b对象,并且给b对象赋值a='a'这个属性,而是直接将this指向了新创建的d对象(实例),并且给d对象赋值了a='a'这个属性,所以new操作符在this的操作中是优先级最高的。