你不知道的javascript--this

人們很容易把 this 理解成指向函數(shù)自身凫乖,這個推斷從英語的語法角度來說是說得通的朴译。但是首先需要消除一些關(guān)于 this 的錯誤認(rèn)識。太拘泥于“this”的字面意思就會產(chǎn)生一些誤解。有兩種常見的對于 this 的解釋,但是它 們都是錯誤的增淹。

  • 默認(rèn)綁定
    思考一下下面這段代碼
function foo () {
  console.log(this.a);
}
var a = 2;
foo();
// 2

在上面的例子中this.a解析為了全局變量中的a,因?yàn)樵诒?例中乌企,函數(shù)調(diào)用時應(yīng)用了 this 的默認(rèn)綁定虑润,因此 this 指向全局對象〖咏停可以通過分析調(diào)用位置來看看 foo() 是如何調(diào) 用的拳喻。在代碼中,foo() 是直接使用不帶任何修飾的函數(shù)引用進(jìn)行調(diào)用的猪腕,因此只能使用 默認(rèn)綁定冗澈,無法應(yīng)用其他規(guī)則。
如果使用嚴(yán)格模式(strict mode)陋葡,那么全局對象將無法使用默認(rèn)綁定亚亲,因此this會綁定 到 undefined:

function foo() {
  'use strict'
  console.log(this.a);
}
var a = 2;
foo();
// TypeError: this is undefined

這里有一個微妙但是非常重要的細(xì)節(jié),雖然 this 的綁定規(guī)則完全取決于調(diào)用位置腐缤,但是只 有foo()運(yùn)行在非strict mode下時捌归,默認(rèn)綁定才能綁定到全局對象;嚴(yán)格模式下與foo() 的調(diào)用位置無關(guān):


function foo() { console.log( this.a );
}
var a = 2;
(function(){ "use strict";
    foo(); // 2
})();

通常來說你不應(yīng)該在代碼中混合使用strict mode和non-strict mode。整個 程序要么嚴(yán)格要么非嚴(yán)格岭粤。然而陨溅,有時候你可能會用到第三方庫,其嚴(yán)格程 度和你的代碼有所不同绍在,因此一定要注意這類兼容性細(xì)節(jié)。

  • 隱式綁定
    另一條需要考慮的規(guī)則是調(diào)用位置是否有上下文對象雹有,或者說是否被某個對象擁有或者包
    含偿渡,不過這種說法可能會造成一些誤導(dǎo)。
    思考下面的代碼:
function foo() { 
  console.log( this.a );
}
var obj={
  a: 2,
  foo: foo 
};
obj.foo(); 
// 2

首先需要注意的是 foo() 的聲明方式霸奕,及其之后是如何被當(dāng)作引用屬性添加到 obj 中的溜宽。 但是無論是直接在 obj 中定義還是先定義再添加為引用屬性,這個函數(shù)嚴(yán)格來說都不屬于 obj 對象质帅。
然而适揉,調(diào)用位置會使用 obj 上下文來引用函數(shù)留攒,因此你可以說函數(shù)被調(diào)用時 obj 對象“擁 有”或者“包含”它。
無論你如何稱呼這個模式嫉嘀,當(dāng) foo() 被調(diào)用時炼邀,它的落腳點(diǎn)確實(shí)指向 obj 對象。當(dāng)函數(shù)引 用有上下文對象時剪侮,隱式綁定規(guī)則會把函數(shù)調(diào)用中的 this 綁定到這個上下文對象拭宁。因?yàn)檎{(diào) 用 foo() 時 this 被綁定到 obj,因此 this.a 和 obj.a 是一樣的瓣俯。
對象屬性引用鏈中只有最頂層或者說最后一層會影響調(diào)用位置杰标。舉例來說:

function foo() { 
  console.log( this.a );
}
var obj2 = { 
  a: 42,
  foo: foo 
};
var obj1 = {
  a: 2,
  obj2: obj2 
};
obj1.obj2.foo(); 
// 42

隱式丟失

一個最常見的 this 綁定問題就是被隱式綁定的函數(shù)會丟失綁定對象,也就是說它會應(yīng)用默認(rèn)綁定彩匕,從而把 this 綁定到全局對象或者 undefined 上腔剂,取決于是否是嚴(yán)格模式。 思考下面的代碼:

function foo() { 
  console.log( this.a );
}
var obj={ 
  a: 2,
  foo: foo
};
var bar = obj.foo; 
// 函數(shù)別名!
var a = "oops, global"; 
// a 是全局對象的屬性 bar(); // "oops, global"

參數(shù)傳遞其實(shí)就是一種隱式賦值驼仪,因此我們傳入函數(shù)時也會被隱式賦值掸犬,所以結(jié)果和上一 個例子一樣。

  • 顯式綁定
    思考下面的代碼:
function foo() { 
  console.log( this.a );
}
var obj = { 
  a:2
};
foo.call( obj ); // 2

通過 foo.call(..)谅畅,我們可以在調(diào)用 foo 時強(qiáng)制把它的 this 綁定到 obj 上登渣。
如果你傳入了一個原始值(字符串類型、布爾類型或者數(shù)字類型)來當(dāng)作 this 的綁定對 象毡泻,這個原始值會被轉(zhuǎn)換成它的對象形式(也就是new String(..)胜茧、new Boolean(..)或者 new Number(..))。這通常被稱為“裝箱”仇味。

  1. 硬綁定
    但是顯式綁定的一個變種可以解決這個問題呻顽。
    思考下面的代碼:
function foo () {
  console.log(this.a);
}
var obj = {
  a: 2
}
var bar = function () {
  foo.call(obj);
}
bar();
setTimeout(bar, 100); // 2
// 硬綁定的 bar 不可能再修改它的 
this bar.call( window ); // 2

我們來看看這個變種到底是怎樣工作的。我們創(chuàng)建了函數(shù) bar()丹墨,并在它的內(nèi)部手動調(diào)用 了 foo.call(obj)廊遍,因此強(qiáng)制把 foo 的 this 綁定到了 obj。無論之后如何調(diào)用函數(shù) bar贩挣,它 總會手動在 obj 上調(diào)用 foo喉前。這種綁定是一種顯式的強(qiáng)制綁定,因此我們稱之為硬綁定

  • new綁定

這是第四條也是最后一條 this 的綁定規(guī)則王财,在講解它之前我們首先需要澄清一個非常常見
的關(guān)于 JavaScript 中函數(shù)和對象的誤解卵迂。 在傳統(tǒng)的面向類的語言中,“構(gòu)造函數(shù)”是類中的一些特殊方法绒净,使用 new 初始化類時會
調(diào)用類中的構(gòu)造函數(shù)见咒。通常的形式是這樣的:
something = new MyClass(..);
JavaScript 也有一個 new 操作符,使用方法看起來也和那些面向類的語言一樣挂疆,絕大多數(shù)開 發(fā)者都認(rèn)為 JavaScript 中 new 的機(jī)制也和那些語言一樣改览。然而下翎,JavaScript 中 new 的機(jī)制實(shí) 際上和面向類的語言完全不同。
首先我們重新定義一下 JavaScript 中的“構(gòu)造函數(shù)”宝当。在 JavaScript 中视事,構(gòu)造函數(shù)只是一些 使用 new 操作符時被調(diào)用的函數(shù)。它們并不會屬于某個類今妄,也不會實(shí)例化一個類郑口。實(shí)際上, 它們甚至都不能說是一種特殊的函數(shù)類型盾鳞,它們只是被 new 操作符調(diào)用的普通函數(shù)而已犬性。
舉例來說,思考一下 Number(..) 作為構(gòu)造函數(shù)時的行為腾仅,ES5.1 中這樣描述它:Number 構(gòu)造函數(shù)
當(dāng) Number 在 new 表達(dá)式中被調(diào)用時乒裆,它是一個構(gòu)造函數(shù):它會初始化新創(chuàng)建的 對象。
使用 new 來調(diào)用函數(shù)推励,或者說發(fā)生構(gòu)造函數(shù)調(diào)用時鹤耍,會自動執(zhí)行下面的操作。

  1. 創(chuàng)建(或者說構(gòu)造)一個全新的對象验辞。
  2. 這個新對象會被執(zhí)行[[原型]]連接稿黄。
  3. 這個新對象會綁定到函數(shù)調(diào)用的this。
  4. 如果函數(shù)沒有返回其他對象跌造,那么new表達(dá)式中的函數(shù)調(diào)用會自動返回這個新對象杆怕。
    我們現(xiàn)在關(guān)心的是第 1 步、第 3 步壳贪、第 4 步陵珍,所以暫時跳過第 2 步,第 5 章會詳細(xì)介紹它违施。 思考下面的代碼:
function foo(a) { 
  this.a = a;
}
var bar = new foo(2); 
console.log( bar.a ); // 2

使用 new 來調(diào)用 foo(..) 時互纯,我們會構(gòu)造一個新對象并把它綁定到 foo(..) 調(diào)用中的 this 上。new 是最后一種可以影響函數(shù)調(diào)用時 this 綁定行為的方法磕蒲,我們稱之為 new 綁定留潦。

優(yōu)先級

new > 顯示綁定 > 隱性綁定 >默認(rèn)綁定

小結(jié)

如果要判斷一個運(yùn)行中函數(shù)的 this 綁定,就需要找到這個函數(shù)的直接調(diào)用位置辣往。找到之后
就可以順序應(yīng)用下面這四條規(guī)則來判斷 this 的綁定對象愤兵。

  1. 由new調(diào)用?綁定到新創(chuàng)建的對象。
  2. 由call或者apply(或者bind)調(diào)用?綁定到指定的對象排吴。
  3. 由上下文對象調(diào)用?綁定到那個上下文對象。
  4. 默認(rèn):在嚴(yán)格模式下綁定到undefined懦鼠,否則綁定到全局對象钻哩。
    一定要注意屹堰,有些調(diào)用可能在無意中使用默認(rèn)綁定規(guī)則。如果想“更安全”地忽略 this 綁 定街氢,你可以使用一個 DMZ 對象扯键,比如 ? = Object.create(null),以保護(hù)全局對象珊肃。
    ES6 中的箭頭函數(shù)并不會使用四條標(biāo)準(zhǔn)的綁定規(guī)則荣刑,而是根據(jù)當(dāng)前的詞法作用域來決定 this,具體來說伦乔,箭頭函數(shù)會繼承外層函數(shù)調(diào)用的 this 綁定(無論 this 綁定到什么)厉亏。這 其實(shí)和 ES6 之前代碼中的 self = this 機(jī)制一樣。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烈和,一起剝皮案震驚了整個濱河市爱只,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌招刹,老刑警劉巖恬试,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疯暑,居然都是意外死亡训柴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門妇拯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幻馁,“玉大人,你說我怎么就攤上這事乖阵⌒猓” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵瞪浸,是天一觀的道長儒将。 經(jīng)常有香客問我,道長对蒲,這世上最難降的妖魔是什么钩蚊? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮蹈矮,結(jié)果婚禮上砰逻,老公的妹妹穿的比我還像新娘。我一直安慰自己泛鸟,他們只是感情好蝠咆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般刚操。 火紅的嫁衣襯著肌膚如雪闸翅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天菊霜,我揣著相機(jī)與錄音坚冀,去河邊找鬼。 笑死鉴逞,一個胖子當(dāng)著我的面吹牛记某,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播构捡,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼液南,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了叭喜?” 一聲冷哼從身側(cè)響起贺拣,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捂蕴,沒想到半個月后譬涡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啥辨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年涡匀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溉知。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡陨瘩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出级乍,到底是詐尸還是另有隱情舌劳,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布玫荣,位于F島的核電站甚淡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捅厂。R本人自食惡果不足惜贯卦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望焙贷。 院中可真熱鬧撵割,春花似錦、人聲如沸辙芍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至庶灿,卻和暖如春注簿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跳仿。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捐晶,地道東北人菲语。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像惑灵,于是被迫代替她去往敵國和親山上。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348