對於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();
$ ("button").click (function (event) {
// $(this) 的值將是 ($("button")) 對象
// 因為這個button對象調用了 click () 方法
console.log ($ (this).prop ("name"));
});
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();
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
})
}
解決方法作為變量進行傳遞時的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/