this到底指向誰的心?

淺談一下this

this這個東西相信對於不少新人來講是個糊塗帳,甚至老手來說,在一些稀奇古怪的狀況下都不必定完徹底全的講清楚.特別是面對腦經急轉彎同樣的面試題,讓人苦笑不得.java

一句話總結

this既不指向函數自身,也不指向函數的詞法做用域.this其實是在函數被調用時發生的綁定,它的指向只取決於函數被誰調用了或者函數在哪裏被調用.es6

5大狀況

由於let聲明的變量在瀏覽器中不是頂層全局的,因此個人全部聲明都用var

  • 1 : this 在全局環境下指向window (默認綁定)

在全局環境下,咱們寫的全部的變量和函數都是window對象裏面的屬性和方法.面試

在瀏覽器中打印以上代碼,獲得這些結果.數組

  • 2 : this函數被誰調用,this在哪裏被調用.(隱式綁定)

這句好像不太好理解,我貼一下代碼,而後逐句解釋一下.瀏覽器

var a  ={  //聲明一個a對象,裏面有個isThis方法
	isThis(){
		console.log(this)
	}
}
a.isThis()//調用一下這個方法
複製代碼

結果以下:app

如今理解一下這句話, this被誰調用, a對象裏面有一個函數isThis, a調用isThis, isThis被a調用, 故this指向了a對象.

咱們再來看第一條:window對象中有個d方法,因此d方法被window調用的時候,this天然指向了window對象.函數

this函數在哪裏被調用 this便指向誰ui

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方法
複製代碼

打印結果:this

首先由於foo賦值給了b,故在b調用本身對象中的方法時,this指向b自身,因此foo函數中的this指向b,在b.log()的時候,log函數是b自己的方法且被b調用,故this指向b,且執行了this.foo,foo被b調用,或者說foo在b中調用,故this指向b,logspa

而在全局中調用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指向的規則,碰見問題逐字分析,必定能夠獲得想要的答案。(如文章中有錯誤之處,但願你們指正,不喜勿噴)

相關文章
相關標籤/搜索