天天看點

javascript this詳解

說到this,最重要的就是this的指向了(這樣說并不準确,因為this隻是函數被調用時所建立的活動對象中的一個屬性而已)。

有些人可能認為this指向的是自身,因為this這個單詞的含義就是如此嘛,不過這種認識應該是錯誤的,不信?來看代碼

javascript this詳解

function foo(num) {  

    console.log("foo" + num);  

    this.count++;  

}  

foo.count = 0;  

for(var i = 0; i < 5; i++) {  

    foo(i);  

console.log(foo.count);  

console.log(count);  

 對于第二行的輸出肯定大家都是知道答案的

那麼第九行的輸出是什麼?(如果this指向的是自身的話foo被調用5次 foo.count應該是5才對,但是!)

javascript this詳解

 事實上this.count并沒有 ++ 由此可以說明,this并不是指向自身。此處,this執行全局變量window

又會有人說,我知道他不是指向自身呀,他明明是指向作用域的嘛(好吧,很長一段時間我也是這麼以為的)。不過事實上這也是不正确的,依舊,我們用一段代碼來證明

javascript this詳解

function foo() {  

    var a = 2;  

    this.bar();  

function bar() {  

    console.log(this.a);  

foo();  

 這段代碼第六行的輸出會是什麼呢?是2嗎?

this并沒有指向自身作用域.看到這裡的你現在一定暴虐的想要知道那this到底是個什麼鬼,那現在我們就開始一層層解析this是什麼

想要知道函數在執行過程中是如何綁定this的,首先要知道函數的調用位置。可能很多人已經可以肯輕松的尋找出函數調用位置,那就輕輕松的看一下我的代碼和你想出的答案是不是一樣的吧

javascript this詳解

function baz() {  

    //目前調用棧:baz  

   //調用位置是全局作用域  

    console.log("baz");  

    bar();//bar的調用位置  

    //目前調用棧:baz -> bar  

   //調用位置是baz中  

    console.log("bar");  

    foo();//foo的調用位置  

    //目前調用棧:baz -> bar -> foo  

   //調用位置是bar中  

    console.log("foo");  

baz();//baz的調用位置  

 看一下上面的代碼,從 baz(); 開始,baz在全局之中,之後baz調用 bar(); 是以bar的調用位置在baz之中,再然後bar調用 foo(); foo的調用位置在baz之中。執行foo函數是的調用棧為baz -> bar -> foo 。

通過函數的互相調用找出調用棧,便可以找出函數的真正調用位置,可是如果代碼量過大可能這樣人工查找很容易出錯,是以我們有更好的方法

javascript this詳解

上圖右側向上的箭頭所滑過的就是foo函數執行的調用棧,棧中的第二個元素就是真正的調用位置。

找到調用位置之後我們來看一下this的綁定符合哪種綁定規則

1.隐式綁定: 看調用位置是否有上下文對象,即是否被某個對象擁有或包含(這裡說的包含可不是用花括号包起來呦)。

javascript this詳解

var a = 3;  

var obj = {  

    a: 2,  

    foo: foo  

};  

obj.foo();  

上面又是一個函數調用裡面輸出this指向的一個值呢,先不說答案,先看一下,這段代碼和上面指出this不是指向作用域的那段代碼的差別是什麼,我想大家都發現了,這次foo函數的調用是 obj.foo() 。foo函數被用作obj對象的一個方法來調用。很明顯根據foo函數的聲明位置來看他并不屬于obj對象,但是~~哈哈,講了好多回但是~~foo函數的調用位置是obj的上下文來引用函數,也就是說他的落腳點是obj對象,那~~他的this自然也就指向他的上下文對象。這樣一分析,答案就顯而易見了,this.a等同于obj.a就是2。

(ps:如果對象屬性的引用是多層的,隻有最後一層會影響調用位置)。

2.顯式綁定: 

   上面的隐式綁定是通過調用一個對象中綁定了函數的屬性來把this隐式的綁定在這個對象上的。是以顯示綁定就是使用一些方法強制将函數綁定在某個對象上,這些方法就是 call(...)  apply(...) 這兩種方法的工作方式是類似的,是以我們就以call()來作為例子分析一下他們的工作過程

這兩個方法第一個參數是一個對象,而被綁定的函數的this就會綁定在這個對象上,看一段代碼

javascript this詳解

    a: 2  

foo.call(obj);  

 foo函數執行時使用了call函數,call函數的第一個參數是obj對象,則foo函數活動對象的this屬性就被綁定在了obj對象上,是以,輸出2.

3.new 綁定

javascript this詳解

function foo(a) {  

   this.a = a;  

var bar = new foo(2);  

console.log(bar.a);  

 使用 new 來調用函數,即發生構造函數調用時,會執行下面操作

建立一個全新的對象

這個對象會被執行[__proto__]的連結(bar.__proto__=foo.prototype)

這個新對象會綁定到函數調用的this上

如果函數沒有傳回其他對象,那麼new表達式中的函數調用會自動傳回這個新對象

當使用new操作符執行foo函數時,就建立了一個全新的對象并且指派給了變量bar。進行原型連結之後(這裡不是我們今天讨論的範圍)foo函數活動對象的this屬性被綁定在新建立的對象bar上,foo函數并沒有傳回值,是以自動傳回new建立的bar對象。

4.預設綁定    預設綁定就是不符合上面任何一種規則是的預設規則

javascript this詳解

var a = 2;  

好了,關于this的四種綁定規則都已經解釋清楚。那有沒有意外情況呢?

是不是意外情況就看你對this的了解有沒有到位,有沒有認真的分析了,下面放兩種特殊情況讓大家轉動一下腦筋

1

javascript this詳解

var bar = obj.foo;  

var a = "global";  

bar();  

 這段代碼唯一的特殊點就是bar引用了obj的foo函數。而答案就變成了global。這是為什麼呢?

首先我們直接來輸出一下bar

javascript this詳解

可以看到bar就是foo()函數的一個别名而已,雖然他是通過obj引用的foo()函數,但他隻是引用到了foo函數,并沒有引用到obj對象的執行上下文,是以他符合的是上面this規則的第四條預設綁定,是以this指向全局輸出global。

2

javascript this詳解

function todo (fn) {  

    fn();  

todo(obj.foo);  

 這段代碼是傳入回掉函數。而todo函數中fn的傳遞依舊隻是傳遞了foo這個函數,并沒有傳遞obj對象的執行上下文,是以在fn()調用的位置沒有任何特殊綁定,符合this規則的預設綁定,this指向全局。不信?我們在todo函數内輸出一下fn看是不是foo函數

javascript this詳解

看來我說的是對的。

一切都解決之後你肯定又會問,那萬一我遇見的函數一下子符合了兩條規則怎麼辦?呃。。。我們來測一下綁定規則的優先級吧

不用說預設綁定的優先級是最低的,那先不用理他,先看一下隐式綁定和顯示綁定誰的優先級更高

javascript this詳解

var obj1 = {  

var obj2 = {  

    a: 3,  

obj1.foo();  

obj2.foo();  

obj1.foo.call(obj2);  

obj2.foo.call(obj1);  

javascript this詳解

看來顯式綁定輕松戰勝隐式綁定,那顯式綁定和new誰的優先級更高呢?好吧,由于new和call(),apply()無法同時使用,是以我沒有寫出他們之間的測試代碼,不過查了些資料之後的結論是new的優先級高于顯式綁定(那位大神可以舉一個new優先級高于現實綁定的例子呢~如果有的話請貼進評論,小女子在此謝過~~)。

那總結以上經驗就是想要判斷this綁定規則,先看new,再看顯式綁定,再看隐式綁定,都沒有則使用預設綁定。

還有很多種例外等着大家去發覺。不過還是一般情況占據多數,是以,隻要正确尋找分析函數調用棧,找到函數調用位置,在函數調用位置上判斷使用那種綁定規則,基本可以正确判斷出this的指向。

再看一些特别的用法

javascript this詳解

function person()  

{  

  this.username="huangwei";  

  this.sayhello=function(){  

    console.log("username:"+this.username);  

  }  

  console.log(this);  

  return this;  

var person=new person();  

person.sayhello();  

console.log(person);  

 運作結果:

javascript this詳解

 注釋掉第八行(return this),執行結果相同.

參考:http://www.cnblogs.com/xiaoruo/p/4492453.html

http://gejiawen.github.io/2015/03/18/javascript/%e5%8c%ba%e5%88%86js%e4%b8%ad%e7%9a%84__proto_

繼續閱讀