this到底指向谁的心?

477 阅读8分钟

浅谈一下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的操作中是优先级最高的。

this 一直以来就是一个比较麻烦的东西,但是只要大家记清楚这几个this指向的规则,遇见问题逐字分析,一定可以得到想要的答案。(如文章中有错误之处,希望大家指正,不喜勿喷)