理解JavaScript中的this關(guān)鍵字

原文:Understanding the "this" keyword in JavaScript

許多人被JavaScript中的this關(guān)鍵字給困擾住了厌处,我想混亂的根源來自人們理所當(dāng)然地認(rèn)為JavaScript中的this應(yīng)該像Java中的this或Python中的self一樣工作态蒂。盡管JavaScript的this有時(shí)有類似的效果,但它跟Java或其他語言中的this是完全不同的丹壕。盡管有點(diǎn)難理解捌木,但它的原理并不神秘。事實(shí)上职辅,this遵循一些簡單的小規(guī)則,這篇文章就對這些規(guī)則作出了解釋聂示。

但首先域携,我想給出一些專業(yè)術(shù)語和一些JavaScript運(yùn)行時(shí)環(huán)境的相關(guān)信息,希望能幫助你構(gòu)建一個(gè)心智模型來更好的理解this鱼喉。

執(zhí)行上下文

每一行JavaScript代碼都運(yùn)行在一個(gè)“執(zhí)行上下文”中秀鞭。JavaScript運(yùn)行時(shí)環(huán)境用一個(gè)棧維持著這些上下文,棧頂?shù)囊粋€(gè)就是當(dāng)前正在運(yùn)行的執(zhí)行上下文扛禽。

有三類可執(zhí)行代碼:全局代碼(global code)锋边,函數(shù)代碼(function code)eval代碼(eval code)编曼。大概的說豆巨,全局代碼是應(yīng)用程序最頂層的代碼,不被包含在任何方法中掐场,函數(shù)代碼是在函數(shù)(function)體中的代碼往扔,eval代碼是被eval解釋的全局代碼。

this對象每次控制進(jìn)入一個(gè)新的執(zhí)行上下文時(shí)會被重新確定指向熊户,直到控制切換到一個(gè)不同的上下文萍膛。this的值取決于兩件事:被執(zhí)行的代碼的類型(global,function,eval)和調(diào)用代碼的對象。

全局對象

所有的JavaScript運(yùn)行時(shí)都有一個(gè)唯一的全局對象嚷堡。他的屬性包括內(nèi)置的對象如MathString卦羡,以及其他由主環(huán)境變量定義的屬性。

在瀏覽器中,全局對象是window對象绿饵。在Node.js中,它就叫作“global object”瓶颠。(我假定你將在瀏覽器中運(yùn)行代碼拟赊,然而,我所說的一切也同樣適用于Node.js粹淋。)

確定this的值

第一條規(guī)則很簡單:this指向全局對象在所有全局代碼中吸祟。由于所有的程序都是由執(zhí)行全局代碼開始的,并且this在給定的執(zhí)行上下文中會被修正桃移,所以屋匕,默認(rèn)的this指全局對象。

當(dāng)控制進(jìn)入一個(gè)新的執(zhí)行上下文時(shí)發(fā)生了什么呢借杰?只有三種this的值發(fā)生改變的情況:方法調(diào)用过吻,new一個(gè)函數(shù)對象,函數(shù)被callapply調(diào)用蔗衡。我將逐一解釋纤虽。

方法調(diào)用

當(dāng)我們通過點(diǎn)(例obj.foo())或者方括號(例obj["foo"])把一個(gè)方法作為一個(gè)對象的屬性來調(diào)用時(shí),this將指向方法體的父對象:

var counter = {
  val: 0,
  increment: function () {
    this.val += 1;
  }
};
counter.increment();
console.log(counter.val); // 1
counter['increment']();
console.log(counter.val); // 2

這是第二條規(guī)則:函數(shù)被當(dāng)作父對象的屬性來調(diào)用時(shí)绞惦,在函數(shù)代碼中的this指向函數(shù)的父對象逼纸。

注意,如果我們直接調(diào)用相同的方法济蝉,即杰刽,不作為父對象的屬性,this將不會指向counter對象:

var inc = counter.increment;
inc();
console.log(counter.val); // 2
console.log(val); // NaN

當(dāng)inc被調(diào)用時(shí)王滤,這里的this不會改變贺嫂,所以它還是指向全局對象。

當(dāng)inc執(zhí)行

this.val += 1;

它等效于執(zhí)行:

window.val += 1;

window.val是undefined淑仆,且undefined1得到NaN涝婉。

new運(yùn)算符

任何JavaScript函數(shù)都可以通過new運(yùn)算符當(dāng)成構(gòu)造函數(shù)使用。new運(yùn)算符創(chuàng)建一個(gè)新對象并且設(shè)置函數(shù)中的this指向調(diào)用函數(shù)的新對象蔗怠。舉個(gè)栗子:

function F (v) {
  this.val = v;
}
var f = new F("Woohoo!");
console.log(f.val); // Woohoo!
console.log(val); // ReferenceError

這就是我們的第三條規(guī)則:在用new運(yùn)算符調(diào)用的函數(shù)代碼中的this指向新創(chuàng)建的對象墩弯。

注意F沒有任何特殊。如果不通過new調(diào)用寞射,this將指向全局對象:

var f = F("Oops!");
console.log(f.val); // undefined
console.log(val); // Oops!

在這個(gè)例子中渔工,F("Oops!")是一個(gè)通常調(diào)用,this沒有指向新的對象桥温,因?yàn)闆]有用new創(chuàng)建新的對象引矩,this繼續(xù)指向全局對象。

Call & Apply

所有JavaScript函數(shù)都有兩個(gè)方法,callapply旺韭,讓你能夠調(diào)用函數(shù)并且清楚的設(shè)置this指向的對象氛谜。apply函數(shù)有兩個(gè)參數(shù):一個(gè)是this指向的對象,一個(gè)(可選)傳進(jìn)函數(shù)的參數(shù)的數(shù)組:

var add = function (x, y) {
      this.val = x + y;
    },
    obj = {
      val: 0
    };
add.apply(obj, [2, 8]);
console.log(obj.val); // 10

call方法和apply方法是完全一樣的区端,只不過要逐個(gè)的傳遞參數(shù)值漫,而不是用一個(gè)數(shù)組:

add.call(obj, 2, 8);
console.log(obj.val); // 10

這是第四條規(guī)則:當(dāng)使用callapply調(diào)用函數(shù)時(shí),函數(shù)代碼中的this被設(shè)置為callapply中的第一個(gè)參數(shù)织盼。

總結(jié)

  • 默認(rèn)的杨何,this指向全局對象
  • 當(dāng)一個(gè)函數(shù)被作為一個(gè)父對象的屬性調(diào)用時(shí),函數(shù)中的this指向父對象
  • 當(dāng)一個(gè)函數(shù)被new運(yùn)算符調(diào)用時(shí)沥邻,函數(shù)中的this指向新創(chuàng)建的對象
  • 當(dāng)使用callapply調(diào)用函數(shù)時(shí)危虱,函數(shù)代碼中的this被設(shè)置為callapply中的第一個(gè)參數(shù)。如果第一個(gè)參數(shù)是null或不是個(gè)對象唐全,this指向全局對象埃跷。

如果你理解并接受了上面的4條規(guī)則,你就能明白this指的是什么了芦瘾。

補(bǔ)充:eval打破上面所有規(guī)則

Remember when I said that code evaluated inside eval is its own type of executable code? Well, that’s true, and it means that the rules for determining what this refers to inside of eval code are a little more complex.

As a first pass, you might think that this directly inside eval refers to the same object as it does in eval’s caller’s context. For example:

var obj = {
  val: 0,
  func: function() { 
    eval("console.log(this.val)");
  }
};
obj.func(); // 0

That works likely as you expect it to. However, there are many cases with eval where this will probably not work as you expect:

eval.call({val: 0}, "console.log(this.val)"); // depends on browser

The output of the above code depends on your browser. If your JavaScript runtime implements ECMAScript 5, this will refer to the global object and the above should print undefined, because it’s an “indirect” call of eval. That’s what the latest versions of Chrome and Firefox do. Safari 5.1 actually throws an error (“The ‘this’ value passed to eval must be the global object from which eval originated”), which is kosher according to ECMAScript 3.

If you want know how this works with eval, you should read Juriy Zaytsev’s excellent, “Global eval. What are the options?” You’ll learn more about eval, execution contexts, and direct vs. indirect calls than you probably ever wanted to know.

In general, you should just avoid using eval, in which case the simple rules about this given previously will always hold true.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捌蚊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子近弟,更是在濱河造成了極大的恐慌缅糟,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祷愉,死亡現(xiàn)場離奇詭異窗宦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)二鳄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門赴涵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人订讼,你說我怎么就攤上這事髓窜。” “怎么了欺殿?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵寄纵,是天一觀的道長。 經(jīng)常有香客問我脖苏,道長程拭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任棍潘,我火速辦了婚禮恃鞋,結(jié)果婚禮上崖媚,老公的妹妹穿的比我還像新娘。我一直安慰自己恤浪,他們只是感情好畅哑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著资锰,像睡著了一般敢课。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绷杜,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音濒募,去河邊找鬼鞭盟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瑰剃,可吹牛的內(nèi)容都是我干的齿诉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼晌姚,長吁一口氣:“原來是場噩夢啊……” “哼粤剧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挥唠,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抵恋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后宝磨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弧关,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年唤锉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了世囊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窿祥,死狀恐怖株憾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晒衩,我是刑警寧澤嗤瞎,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站浸遗,受9級特大地震影響猫胁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜跛锌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一弃秆、第九天 我趴在偏房一處隱蔽的房頂上張望届惋。 院中可真熱鬧,春花似錦菠赚、人聲如沸脑豹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘩欺。三九已至,卻和暖如春拌牲,著一層夾襖步出監(jiān)牢的瞬間俱饿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工塌忽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拍埠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓土居,卻偏偏與公主長得像枣购,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子擦耀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 導(dǎo)語 不得不說棉圈,作為一名初級的前端開發(fā)者,this關(guān)鍵字這個(gè)問題對于我來說一直是一個(gè)痛點(diǎn)眷蜓,什么是this分瘾?什么是函...
    Nicole_tiny閱讀 530評論 0 4
  • 1. this之謎 在JavaScript中,this是當(dāng)前執(zhí)行函數(shù)的上下文账磺。因?yàn)镴avaScript有4種不同的...
    百里少龍閱讀 999評論 0 3
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,233評論 0 4
  • 前段時(shí)間算了一個(gè)命垮抗,覺得心拔涼拔涼的氏捞,甚至非常后悔,為啥花錢給自己找不痛快冒版? 輾轉(zhuǎn)反思液茎,寤寐思服。 虛無縹緲的命運(yùn)...
    奢奢閱讀 595評論 0 1
  • 文/孤鳥差魚 黑夜 忘了白天 愛的聲嘶力竭
    孤鳥差魚閱讀 213評論 0 1