清晰理解JavaScript的 “this” 并掌握它

無論新入行的開發者,還是老手,也時常會被關鍵詞“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/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容