深入淺出 JavaScript 中的 this

來源:互聯網
上載者:User

標籤:

本文來自:http://www.ibm.com/developerworks/cn/web/1207_wangqf_jsthis/

JavaScript 語言中的 this

由於其運行期綁定的特性,JavaScript 中的 this 含義要豐富得多,它可以是全域對象、當前對象或者任意對象,這完全取決於函數的調用方式。JavaScript 中函數的調用有以下幾種方式:作為對象方法調用,作為函數調用,作為建構函式調用,和使用 apply 或 call 調用。下面我們將按照調用方式的不同,分別討論 this 的含義。

作為對象方法調用

在 JavaScript 中,函數也是對象,因此函數可以作為一個對象的屬性,此時該函數被稱為該對象的方法,在使用這種調用方式時,this 被自然綁定到該對象。

清單 2. point.js
 var point = {  x : 0,  y : 0,  moveTo : function(x, y) {      this.x = this.x + x;      this.y = this.y + y;      }  };  point.moveTo(1, 1)//this 綁定到當前對象,即 point 對象
作為函數調用

函數也可以直接被調用,此時 this 綁定到全域對象。在瀏覽器中,window 就是該全域對象。比如下面的例子:函數被調用時,this 被綁定到全域對象,接下來執行指派陳述式,相當於隱式的聲明了一個全域變數,這顯然不是調用者希望的。

清單 3. nonsense.js
 function makeNoSense(x) {  this.x = x;  }  makeNoSense(5);  x;// x 已經成為一個值為 5 的全域變數

對於內建函式,即聲明在另外一個函數體內的函數,這種綁定到全域對象的方式會產生另外一個問題。我們仍然以前面提到的 point 對象為例,這次我們希望在 moveTo 方法內定義兩個函數,分別將 x,y 座標進行平移。結果可能出乎大家意料,不僅 point 對象沒有移動,反而多出兩個全域變數 x,y。

清單 4. point.js
 var point = {  x : 0,  y : 0,  moveTo : function(x, y) {      // 內建函式     var moveX = function(x) {      this.x = x;//this 綁定到了哪裡?    };     // 內建函式    var moveY = function(y) {     this.y = y;//this 綁定到了哪裡?    };     moveX(x);     moveY(y);     }  };  point.moveTo(1, 1);  point.x; //==>0  point.y; //==>0  x; //==>1  y; //==>1

這屬於 JavaScript 的設計缺陷,正確的設計方式是內建函式的 this 應該綁定到其外層函數對應的對象上,為了規避這一設計缺陷,聰明的 JavaScript 程式員想出了變數替代的方法,約定俗成,該變數一般被命名為 that。

清單 5. point2.js
 var point = {  x : 0,  y : 0,  moveTo : function(x, y) {       var that = this;      // 內建函式     var moveX = function(x) {      that.x = x;      };      // 內建函式     var moveY = function(y) {      that.y = y;      }      moveX(x);      moveY(y);      }  };  point.moveTo(1, 1);  point.x; //==>1  point.y; //==>1
作為建構函式調用

JavaScript 支援物件導向式編程,與主流的物件導向式程式設計語言不同,JavaScript 並沒有類(class)的概念,而是使用基於原型(prototype)的繼承方式。相應的,JavaScript 中的建構函式也很特殊,如果不使用 new 調用,則和普通函數一樣。作為又一項約定俗成的準則,建構函式以大寫字母開頭,提醒調用者使用正確的方式調用。如果調用正確,this 綁定到新建立的對象上。

清單 6. Point.js
 function Point(x, y){     this.x = x;     this.y = y;  }
使用 apply 或 call 調用

讓我們再一次重申,在 JavaScript 中函數也是對象,對象則有方法,apply 和 call 就是函數對象的方法。這兩個方法異常強大,他們允許切換函數執行的上下文環境(context),即 this 綁定的對象。很多 JavaScript 中的技巧以及類庫都用到了該方法。讓我們看一個具體的例子:

清單 7. Point2.js
 function Point(x, y){     this.x = x;     this.y = y;     this.moveTo = function(x, y){         this.x = x;         this.y = y;     }  }  var p1 = new Point(0, 0);  var p2 = {x: 0, y: 0};  p1.moveTo(1, 1);  p1.moveTo.apply(p2, [10, 10]);

在上面的例子中,我們使用建構函式產生了一個對象 p1,該對象同時具有 moveTo 方法;使用對象字面量建立了另一個對象 p2,我們看到使用 apply 可以將 p1 的方法應用到 p2 上,這時候 this 也被綁定到對象 p2 上。另一個方法 call 也具備同樣功能,不同的是最後的參數不是作為一個數組統一傳入,而是分開傳入的。

換個角度理解

如果像作者一樣,大家也覺得上述四種方式不方便記憶,過一段時間後,又搞不明白 this 究竟指什麼。那麼我向大家推薦 Yehuda Katz 的這篇文章:Understanding JavaScript Function Invocation and “this”。在這篇文章裡,Yehuda Katz 將 apply 或 call 方式作為函數調用的基本方式,其他幾種方式都是在這一基礎上的演變,或稱之為文法糖。Yehuda Katz 強調了函數調用時 this 綁定的過程,不管函數以何種方式調用,均需完成這一綁定過程,不同的是,作為函數調用時,this 綁定到全域對象;作為方法調用時,this 綁定到該方法所屬的對象。

結束?

通過上面的描述,如果大家已經能明確區分各種情況下 this 的含義,這篇文章的目標就已經完成了。如果大家的好奇心再強一點,想知道為什麼 this 在 JavaScript 中的含義如此豐富,那就得繼續閱讀下面的內容了。作者需要提前告知大家,下面的內容會比前面稍顯枯燥,如果只想明白 this 的含義,閱讀到此已經足夠了。如果大家不嫌枯燥,非要探尋其中究竟,那就一起邁入下一節吧。

 

回頁首

函數的執行環境

JavaScript 中的函數既可以被當作普通函數執行,也可以作為對象的方法執行,這是導致 this 含義如此豐富的主要原因。一個函數被執行時,會建立一個執行環境(ExecutionContext),函數的所有的行為均發生在此執行環境中,構建該執行環境時,JavaScript 首先會建立 arguments變數,其中包含調用函數時傳入的參數。接下來建立範圍鏈。然後初始設定變數,首先初始化函數的形參表,值為 arguments變數中對應的值,如果 arguments變數中沒有對應值,則該形參初始化為 undefined。如果該函數中含有內建函式,則初始化這些內建函式。如果沒有,繼續初始化該函數內定義的局部變數,需要注意的是此時這些變數初始化為 undefined,其賦值操作在執行環境(ExecutionContext)建立成功後,函數執行時才會執行,這點對於我們理解 JavaScript 中的變數範圍非常重要,鑒於篇幅,我們先不在這裡討論這個話題。最後為 this變數賦值,如前所述,會根據函數調用方式的不同,賦給 this全域對象,當前對象等。至此函數的執行環境(ExecutionContext)建立成功,函數開始逐行執行,所需變數均從之前構建好的執行環境(ExecutionContext)中讀取。

Function.bind

有了前面對於函數執行環境的描述,我們來看看 this 在 JavaScript 中經常被誤用的一種情況:回呼函數。JavaScript 支援函數式編程,函數屬於一級對象,可以作為參數被傳遞。請看下面的例子 myObject.handler 作為回呼函數,會在 onclick 事件被觸發時調用,但此時,該函數已經在另外一個執行環境(ExecutionContext)中執行了,this 自然也不會綁定到 myObject 對象上。

清單 8. callback.js
 button.onclick = myObject.handler;

這是 JavaScript 新手們經常犯的一個錯誤,為了避免這種錯誤,許多 JavaScript 架構都提供了手動綁定 this 的方法。比如 Dojo 就提供了 lang.hitch,該方法接受一個對象和函數作為參數,返回一個新函數,執行時 this 綁定到傳入的對象上。使用 Dojo,可以將上面的例子改為:

清單 9. Callback2.js
 button.onclick = lang.hitch(myObject, myObject.handler);

在新版的 JavaScript 中,已經提供了內建的 bind 方法供大家使用。

eval 方法

JavaScript 中的 eval 方法可以將字串轉換為 JavaScript 代碼,使用 eval 方法時,this 指向哪裡呢?答案很簡單,看誰在調用 eval 方法,調用者的執行環境(ExecutionContext)中的 this 就被 eval 方法繼承下來了。

 

回頁首

結束語

本文介紹了 JavaScript 中的 this 關鍵字在各種情況下的含義,雖然這隻是 JavaScript 中一個很小的概念,但藉此我們可以深入瞭解 JavaScript 中函數的執行環境,而這是理解閉包等其他概念的基礎。掌握了這些概念,才能充分發揮 JavaScript 的特點,才會發現 JavaScript 語言特性的強大。

深入淺出 JavaScript 中的 this

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.