理解JavaScript中的“this”


對於javascript的初學者來說,一般對“this”關鍵字都感到非常迷惑。本文的目的旨在讓你全面的了解“this”,理解在每一個情景下如何使用“this”,希望通過本文,可以幫助同學們不在害怕“this”!!

 

從生活中發現

其實“this”就是我們平時用的一個代詞。打個簡單的比喻:

     “小豆豆是一個很幽默的人,非常喜歡看《暴走漫畫》”

但是你也可以這樣寫:

     “小豆豆是一個很幽默的人,小豆豆非常喜歡看《暴走漫畫》”

但是日常生活中我們會一直用這種方式來描述一個人嗎?如果你的回答是Yes,好吧,估計再也沒有人願意跟你做朋友了,我沒騙你…(開個玩笑:-) )。   所以,人類就發明了這樣一種看似高端、洋氣、上檔次的代名詞,說白了javascript中的“this”也就是這么一個東東。

一個簡單的例子:

person = {
 firstName: "zhou",
 lastName: "Quan",
 
 fullName:function(){
 alert(person.firstName + " " + person.lastName);//Zhou Quan
 
 alert(this.firstName + " " + this.lastName);//Zhou Quan
 }
}
 
person.fullName();

我們可以根據前面所說的方式來理解這個程序的結果, 但是這僅僅停留在表面而已,下面我們再進行更深一步的理解。

 

this基礎

首先,我們知道javascript中所有的函數都擁有屬性,就像一個對象也擁有屬性一樣。當一個函數被執行時,他就擁有了“this”屬性--調用this所在函數的對象。this永遠指向一個獨一無二的對象,這個對象通常包含一個函數/方法,雖然它也可以被用在一個函數之外的全局作用域中(global scope)。注意:在嚴格模式(strict mode)中,在全局函數或者匿名函數中this的值為“undefined”(也就是說this沒有綁定任何對象)。

“this”被用在一個函數內部(假設是function A),那么它包含的值是調用“function A”的對象。我們需要this來訪問調用“function A”的對象的屬性和方法,特別是當我們不知道到這個對象名叫什么的時候,或者這個對象沒有名字的時候。其實,this就是調用當前函數的對象的一個代名詞!

我們來舉一個簡單的例子:

var person = {
 firstName: "zhou",
 lastName: "Quan",
 //this將為person對象,因為person對象會調用FullName方法
 FullName:function(){
 alert(this.firstName + " " + this.lastName); //zhou Quqn
 }
}
person.FullName();
 
再來看一個jquery中this的簡單應用:
$ ("button").click (function (event) {
 // $(this) 的值將是 ($("button")) 對象
 // 因為這個button對象調用了 click () 方法
 console.log ($ (this).prop ("name"));
 });
上面的代碼中,$(this)綁定到了button對象,因為jquery庫將$(this)綁定到調用點擊事件的對象。

 

this中存在的最大的問題

this是沒有被指定值的,直到有一個對象調用了this所在的這個函數(我們暫且給它一個稱號:this Function)。

也就是說,只有當有一個對象調用了this Function之后,this Fuction中的this才被指定!在大多數情況下,this Function被調用之后,this都會被指定一個值,但是,也有少數的情況下this是沒有值的,這個問題我們將在后面進一步探討。

 

全局作用域(global scope)中的this

當一段代碼在瀏覽器中執行時,所有的全局變量和函數都是在“window”對象上定義的。因此,當”this”被用在全局函數中是,他指定的值是”window”對象。我們來看下面一段代碼:

var firstName = "Peter",
 lastName = "Ally";
 
 function showFullName () {
 // "this" inside this function will have the value of the window object
 // because the showFullName () function is defined in the global scope, just like the firstName and lastName
 console.log (this.firstName + " " + this.lastName);
 }
 
 var person = {
 firstName :"Penelope",
 lastName :"Barrymore",
 showFullName:function () {
 // "this" on the line below refers to the person object, because the showFullName function will be invoked by person object.
 console.log (this.firstName + " " + this.lastName);
 }
 }
 
 showFullName (); // Peter Ally
 
 // window is the object that all global variables and functions are defined on, hence:
 window.showFullName (); // Peter Ally
 
 // "this" inside the showFullName () method that is defined inside the person object still refers to the person object, hence:
 person.showFullName (); // Penelope Barrymore

 

解決回調函數中的this問題

當包含this的回調函數作為參數傳遞時我們會遇到這樣這個問題:

// We have a simple object with a clickHandler method that we want to use when a button on the page is clicked
 var user = {
 name:"zhouquan",
 age:21,
 clickHandler:function (event) {
 console.log (this.name + " " + this.age);
 }
 }
 
 // The button is wrapped inside a jQuery $ wrapper, so it is now a jQuery object
 // And the output will be undefined because there is no data property on the button object
 $("button").click (user.clickHandler); //undefined

在上面的代碼中,我們知道$(“button”)是一個對象,”user.clickHandler”作為click()方法的回調函數。user.clickHandler方法中的this不再是指向user對象,它現在指向的是調用點擊事件的這個button對象。即使我們是使用“user.clickHandler”來調用clickHandler方法,但是clickHandler()方法是在button對象作為上下文(Context)的環境中運行的,所以,this指向的是button對象。

從這一點上我們可以看出,當上下文(Context)改變--當我們在其他的對象上執行一個方法。“this”所指向的不在是以前的那個對象,而是現在調用這個方法的新的對象。

為了解決這個問題,我們可以使用apply, call和bind的方法來指定“this”所指向的對象。所以上面代碼的最后一行只需改為:

$("button").click (user.clickHandler.bind (user)); //zhouquan 21

 

解決閉包(closure)中的this問題

另外一種容易讓我們感到困擾的是當我們使用了一個內部函數(閉包)的時候。首先我們應該明確的一點就是:閉包中不能通過“this”關鍵字來訪問外邊函數的this變量,因為閉包中的this他指向的window對象。來看一個例子:

var user = {
 country: "china",
 data:[
 {name:"ZhouYi", age:21},
 {name:"ZhouEr", age:22}
 ],
 
 clickHandler:function(){
 //在這個作用域中使用this是OK的,他指向的是user對象
 this.data.forEach(function(person){
 //但是這個內部函數的this不再指向user對象
 
 console.log("What is This referring to? " + this); //Object Window
 
 console.log(person.name + " is come from " + this.country); 
 //ZhouYi is come from undefined
 //ZhouEr is come from undefined
 })
 }
};
user.clickHandler();
內部函數的this不能訪問外部函數的this,所以內部函數的this就指向了全局window對象。為了解決這個問題,我們一般都是采用中間變量來保存外部函數的this:
clickHandler:function(){
 //在這個作用域中使用this是OK的,他指向的是user對象
 //我們定義一個theUserObj來保存this的值
 var theUserObj = this;
 this.data.forEach(function(person){
 //現在我們再用theUserObj來訪問user對象的屬性就沒問題了
 console.log(person.name + " is come from " + theUserObj.country); 
 //ZhouYi is come from china
 //ZhouEr is come from china
 })
 }
上面的代碼中,我們用一個中間變量“theUserObj”來保存外部函數的this,以便在forEach內部函數中可以通過它來調用user對象的屬性。中間變量叫什么名字取決於你自己,大部分同學喜歡用“that”,個人覺得這個不夠形象,所以我喜歡用theUserObj這種一看就明白的詞。

 

解決方法作為變量進行傳遞時的this問題

當我們把一個對象里面的函數作為參數傳遞給另外一個對象時,this所指定的對象往往會超出我們的想象,來看個例子吧:

var data = [
{name: "ZhouYi", age:21},
{name: "ZhouEr", age:22}
];
 
var user = {
 //這里的data屬性是屬於user對象的
 data :[
 {name:"ZhouYi", age: 31},
 {name:"ZhouEr", age: 32}
 ],
 showData:function(){
 console.log(this.data[0].name + " " + this.data[0].age) ;
 }
}
 
//把user.showData賦值給一個變量
var showUserData = user.showData;
//當我們執行這個showUserData函數時,輸出的數據來自全局的data(最外層定義的),而不是來自與user對象中的data.
showUserData(); //ZhouYi 21
解決這個問題的方式跟前面提到的解決回調函數中的this問題類似,我們還是采用apply, call和bind的方法來綁定指定的上下文,也就是this該指向哪個對象。
var showUserDate = user.showData.bind(user);

 

解決方法借用中的this問題

何謂方法調用?打個簡單的比方,有兩個對象A和B,A、B有相同的屬性,但是B比A還多了一個方法,這個方法是對B中的數據進行處理,現在我們想把B這種方法也給A用用:

var gameController = {
 scores : [20, 34, 55, 46, 77],
 avgScore: null,
 players:[
 {name:"Tommy", playerID:987, age:23},
 {name:"Pau", playerID: 87, age: 33}
 ]
}
 
var appController = {
 scores: [900, 845, 809, 950],
 avgScore: null,
 avg: function(){
 var sumOfSores = this.scores.reduce(function(prev, cur, index, array){
 return prev + cur;
 });
 
 this.avgScore = sumOfSores /this.scores.length;
 }
 
}
//由於avg()是被appController調用的,所以avg中的this指向的是appController對象。
gameController.avgScore = appController.avg();
console.log(gameController.avgScore); //undefined
如何解決這個問題呢? 為了讓appController.avg()指向gameController對象,我們可以使用apply()方法:
 // Note that we are using the apply () method, so the 2nd argument has to be an array—the arguments to pass to the appController.avg () method.
 appController.avg.apply (gameController, gameController.scores);
 
 // The avgScore property was successfully set on the gameController object, even though we borrowed the avg () method from the appController object
 console.log (gameController.avgScore); // 46.4
 
 // appController.avgScore is still null; it was not updated, only gameController.avgScore was updated
 console.log (appController.avgScore); // null
注意我們這里使用了兩個參數,第一個參數表示this的上下文,也就是this所指向的對象;第二個參數表示要進行運算的數據。當然啦,我們還可以通過這種方式達到同樣的效果:

gameController.avg = appController.avg;
gameController.avg();
 
          

 

小結

希望我寫的這些東西可以給你帶來一些幫助,如果你覺得哪個地方存在不足,請你在評論處提出來,我將及時完善,免得誤導后面的同學;如果你覺得這篇文章講得還可以,請幫我推薦給更多的有需要的同學閱讀,謝謝!

 
        

 

 

參考資料:http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM