無論新入行的開發者,還是老手,也時常會被關鍵詞“this”所迷惑。這篇文章的目標就是全面地解釋this。當你讀完這篇文章的時候,this將不再是你在JavaScript領域中的一個難題。我們將會在每個例子中了解如何使用this,包括最棘手最難以捉摸的部分。
我們使用this類似于我們在日常交談中使用代詞一樣。我們會寫到“約翰跑得很快因為‘他’正在趕火車”。
注意到這里的“他”。我們也可以寫成“約翰跑得很快因為‘約翰’正在趕火車”。我們一般不會重復使用人名“約翰”,我們如果這樣做了,我們的家人和朋友肯定會覺得我們腦子出了問題。在同樣優雅的語言,JavaScript中,我們運用“this”作為一個捷徑,或者指示物;它指示著一個對象,相當于語境中的主語,或者執行代碼時的對象。
看看以下的例子:
var person = {
? firstName: "Penelope",
? lastName: "Barrymore",
? fullName:function () {
? ?//注意這里我們可以使用this就跟我們例句中的“他”一樣
? console.log(this.firstName + "" + this.lastName);
? //我們也可以這樣子寫:
? console.log(person.firstName + "" + person.lastName);
? }
}
在以上例子中,如果我們使用person.firstName和person.lastName,這段代碼會變得模糊不清:如果已經有一個全局變量person,當我們調用person.firstName的時候可能會訪問到全局變量person中的屬性,同時也給排除故障帶來了困難。所以我們運用this來使得我們的代碼美觀的同時,還更為精確。
就像例句中的代詞“他”指代著它的對象,this也指代著使用它的函數中的執行對象。this不但指代著它的對象,同時還包含著指代目標對象的值。就像代詞一樣,this也可以理解為語境中調出對象的捷徑。
JavaScript關鍵詞this基礎知識
首先,所有JavaScript的函數都有屬性,就像所有的對象都有屬性一樣。當一個函數被調用時,它就會拉取this屬性:一個變量包含了函數執行時對象的數值。
this只會調用單個對象((并包含該目標的值)。注意,當我們運用到嚴格模式的時候,this在全局函數和匿名函數中保留未定義的值是不會上行到任何對象的。
當this在某個函數(假設函數A)中被調用時,它包含了被函數A調用對象的值。我們需要通過this來獲取被調用對象的屬性,特別是當我們調用對象沒有名字的時候。實際上,this也僅僅是一個為調用對象提供的捷徑。
再來看一次this最基本的例子:
varperson = {
? firstName:"Penelope",
? lastName:"Barrymore",
? //當this在“showFullName”函數中被調用,且該函數被定義在了person對象中
? // this就會包含了對象person的值因為person在showFullName被調用了
? showFullName ()?
? showFullName: function () {
? ? console.log(this.firstName + " " + this.lastName);
? }
}
person.showFullName();//輸出Penelope Barrymore
同時也來看看jQuery下this的例子:
//一段非常普通的jQuery代碼
$ ("button").click (function(event) {
? // $(this)將會附有按鈕($("button"))對象的值
? ?//因為按鈕對象被click ()函數調用了
? console.log($ (this).prop ("name"));
});
jQuery語法中的$(this),與JavaScript中關鍵字this有一樣的功能。這里的$(this)運用在了一個匿名函數中,并且這個匿名函數在按鈕click()上。$(this)上行到了按鈕對象,是因為jQuery庫中,$(this)被綁定在了被click()調用的對象上。因此,即使$(this)在匿名函數中被定義了,它也會含有jQuery按鈕($(“button”))對象的值。
注意按鈕在HTML中屬于DOM元素,同時也是一個對象,在上述例子中它就是一個jQuery對象因為我們把它包裝在jQuery $()函數中
有關this的“原來如此”
如果你了解下面這個規則,你就會非常清晰的了解this: this只會在定義this的函數調用了某個對象時才會被附上值。這里我們就把定義this的函數稱為“this函數”吧。
即使看上去this出現在了對象被定義的地方,this在“this函數”被調用前都不會被真正地附上值,而且它的值僅僅決定于被“this函數”調用的對象。絕大多數情況下,this都會附上被調用對象的值,然而在少量情況下,this不會附上被調用對象的值。稍后的文章中會提到。
this用在全局作用域中
當代碼運行在全局作用域中,所有全局變量和函數都被定義在了window對象中,因此當我們將this運用在全局函數中時,它指代(并含有)頁面JavaScript主容器window對象的值(不包括嚴格模式,在之前有提到過)。
例如:
var firstName = "Peter",
lastName = "Ally";
function showFullName () {
? console.log(this.firstName + " " + this.lastName);
}
//這個函數里的this就含有window對象的值
//因為showFullName ()函數被定義為了全局函數,就像firstName和lastName?一樣
//下面的this指代了person對象
//因為showFullName()函數被定義在了person對象中
var person = {
? firstName:"Penelope",
? lastName:"Barrymore",
? showFullName:function() {
? ? console.log(this.firstName + " " + this.lastName);
? }
}
?showFullName (); // Peter Ally?
?// window對象是所有全局變量和全局函數被定義的地方,所以:
window.showFullName (); ?//輸出Peter Ally?
?//在對象person中定義的函數showFullName()中的this依舊指代 對象person
person.showFullName(); ?//輸出PenelopeBarrymore
this最迷惑人的幾個地方
以下幾種情況是this最為迷惑的情況:當我們借用函數中包含了this,當我們用含有this的方法定義變量,當一個函數用this去傳遞回調函數和當this出現在閉包里的時候。我們將會一一舉例說明并弄清楚this在這些情況下的用法。
一、當this去傳遞回調函數
當我們使用一個帶有this的方法作為一個參數出現在回調函數中的時候,事情將會變得復雜:
//下面有一個單一對象,還有一個會被頁面按鈕觸發的方法clickHandler
var user = {
? data:[
? ? {name:"T. Woods", age:37},
? ? {name:"P. Mickelson", age:43}
? ],
? clickHandler:function(event) {
? ? varrandomNum = ((Math.random () * 2 | 0) + 1) - 1; ?// 0到1之間的隨機數
? ? ?//下面這一行代碼會在上面的data數組中隨機抽取人名和年齡
? ? console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
? }
}
//按鈕被包裝在了jQuery里面,所以現在它是一個jQuery對象
//但是會輸出undefined因為在按鈕對象上沒有任何數據
$("button").click (user.clickHandler); ?//報錯:Cannot read property '0' of undefined
在以上的代碼中,因為按鈕($(“button”))本身就是一個對象,這里將方法user.clickHandler作為了回調函數傳遞數據,我們知道當this在user.clickHandler方法中時,它將不會指代對象user。this僅會指代user.clickHandler里面的對象,因為this被定義在了這個方法里面。而user.clickHandler被按鈕所調用,所以user.clickHandler將會在按鈕的點擊時被執行。
注意到即使我們利用代碼user.clickHandler調用了clickHandler()方法,這個方法本身將會被按鈕調用,而且在這個語境中,this指代對象是按鈕對象($(“button”))。
在這里我們可以很明顯的看到語境的變化:當我們在其他對象上執行一個方法,而不是在這個對象當初被定義的地方的時候,this將不會指代原來的對象,而是會指代著被定義this的方法所調用的對象。
當我們想讓this.data指代對象data中的屬性時,我們可以用到bind(),apply()和call()來對this的值做出指定
要解決這個問題,我們可以使用方法bind():
把下面這行代碼:
$ ("button").click(user.clickHandler);
改寫成:
$ ("button").click(user.clickHandler.bind (user));
二、當this在閉包中
另外一個容易混亂的地方就是當this用在了閉包中。非常重要的一點,閉包是沒辦法獲取到閉包外其它函數中的this,因為this只會被定義它的函數本身所獲取。
varuser = {
? tournament:"The Masters",
? data: [
? ? {name:"T. Woods", age:37},
? ? {name:"P. Mickelson", age:43}
? ],
? clickHandler:function () {
? //這里運用this.data是沒有問題的
? //因為this指代了對象user,所以this擁有了對象user的屬性
? ? this.data.forEach(function (person) {
? ? ? //但是在這個匿名函數中,this不再指代對象user?
? ? ? //因為閉包沒辦法獲取外在函數中的this的值
? ? ? console.log ("What is This referringto? " + this); ?//? window對象
? ? ? console.log (person.name + " isplaying at " + this.tournament);
? ? ? // T. Woods is playing at undefined?
? ? ? // P. Mickelson is playing at undefined?
? ? })
? }
}
user.clickHandler();//現在this就指代了window對象
在匿名函數中的this不能獲取外在函數中this的值,所以它上行到了全局對象window。
若要修正這段代碼,我們只需要在進入函數forEach之前將this的值賦值到另外一個變量上:
var user = {
? tournament:"TheMasters",
? data:[
? ? {name:"T. Woods", age:37},
? ? {name:"P. Mickelson", age:43}
? ],
? clickHandler:function(event) {
? //若要獲取當this還指代著對象user時候的值,,我們要在這里設置另外一個變量
? //把this的值賦值到了theUserObj變量上
? vartheUserObj = this;
? this.data.forEach(function (person) {
? ? //我們現在使用的是theUserObj.tournament?
? ? console.log (person.name + " isplaying at " + theUserObj.tournament);
? ? })
? }
}
user.clickHandler(); ?//輸出T. Woods is
playing at The Masters? ?//輸出P. Mickelsonis playing at The Masters
三、當含有this的函數被用做定義變量
當我們用函數去定義一個變量時,this會上行到其他對象上:
//這里的變量data是一個全局變量
var data = [
? {name:"Samantha",age:12},
? {name:"Alexis",age:14}
];
var user = {
//這里的變量data是對象user的屬性
? data:[
? ? {name:"T.Woods", age:37},
? ? {name:"P.Mickelson", age:43}
? ],
? showData:function(event) {
? varrandomNum = ((Math.random () * 2 | 0) + 1) - 1; ?//0到1的隨機數字
//這一行代碼會在data數組中隨機抽取并輸出一個人名
?console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
? }
}
?//將user.showData定義到一個變量中
var showUserData = user.showData;
?//當執行showUserData函數的時候,控制臺輸出值是從全局變量數組data中抽取出來的
//而不是對象user中的data數組
showUserData(); ?// Samantha 12 (從全局變量數組抽取)?
要修改這段代碼,我們也可以用到綁定值的方法bind()將this賦值:
//將showData方法綁定在了對象user上
var showUserData = user.showData.bind(user);
?//現在我們可以獲取user對象的值了,因為this上行到了user對象上
showUserDat(); ?//輸出P. Mickelson 43
四、當this出現在借用函數中
借用函數在JavaScript開發中十分常見,作為一個JavaScript開發員,我們會時常見到這樣的例子。這也是我們需要掌握的一個地方。
讓我們看下當this出現在借用方法的情況:
//這里有兩個對象,其中一個包含了avg()函數,另一個則沒有
//所以我們借用了avg()函數
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 () {
? ? varsumOfScores = this.scores.reduce (function (prev, cur, index, array) {
? ? return prev + cur;
? });
? this.avgScore= sumOfScores / this.scores.length;
? }
}
//如果我們運行下面這段代碼
// gameController.avgScore屬性將會被賦上對象appController里scores數組里的值
gameController.avgScore= appController.avg();
函數avg()里的this將不會指代對象gameController,而會指代appController對象,因為appController對象被調用了。
要想修改這段代碼,使appController.avg()里的this指代gameController,我們可以用到apply()方法:
//這里我們用到了apply(),
//所以gameController.scores必須是一個傳遞數據到appController.avg()方法的數組
appController.avg.apply (gameController,gameController.scores);
?// avgScore屬性被成功的設置到了對象gameController
//即使我們借用了對象appController中的方法
console.log (gameController.avgScore); //46.4?
?//appController.avgScore仍然保持空值null,它并沒有被更新
//僅僅只有gameController.avgScore被更新了
console.log(appController.avgScore); ? // null
對象gameController借用了對象appController中的方法avg()。在方法appController.avg()里的this被設置指代對象gameController因為我們將gameController當作了apply()方法中的第一個參數,在apply()方法中第一個參數總會設置this的值
結束語:
我希望通過讀完這篇文章之后,你們可以擁有足夠的只是去應對JavaScript中的關鍵詞this。一定要記住this只會被賦上“this函數”所調用的對象的值。
祝你們可以享受編程的樂趣!
譯自:
http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/