【你不知道的JavaScript】(四)this的全面解析

一滩报、關于this

1. 為什么使用this

  • this 提供了一種更優(yōu)雅的方式來隱式“傳遞”一個對象引用,因此可以將 API 設計 得更加簡潔并且易于復用。
  • this 使函數(shù)可以自動引用合適的上下文對象呛伴。

2. 對this的誤解

  • 指向自身
  • 指向函數(shù)的作用域

3. this是什么臣疑?

  • this 既不指向函數(shù)自身也不指向函數(shù)的詞法作用域;
  • this的指向馒胆,是在函數(shù)被調(diào)用的時候確定的缨称,也就是執(zhí)行上下文被創(chuàng)建時確定的;
  • this 的指向和函數(shù)聲明的位置沒有任何關系祝迂,只取決于函數(shù)的調(diào)用位置(也就是函數(shù)的調(diào)用方法)睦尽;
  • 在函數(shù)執(zhí)行過程中,this一旦被確定型雳,就不可更改了当凡。
var a = 10;
var obj = {
    a: 20
}

function fn () {
    this = obj; // 這句話試圖修改this,運行后會報錯
    console.log(this.a);
}

fn();

二纠俭、this的全面解析

(一)調(diào)用位置

調(diào)用位置就是函數(shù)在代碼中被調(diào)用的位置(而不是聲明的位置)沿量。

尋找調(diào)用位置就是尋找“函數(shù)被調(diào)用的位置”,其中最重要的是要分析調(diào)用棧(就是為了到達當前執(zhí)行位置所調(diào)用的所有函數(shù))冤荆。我們關心的調(diào)用位置就在當前正在執(zhí)行的函數(shù)的前一個調(diào)用中朴则。

調(diào)用棧又稱“執(zhí)行棧”钓简,棧是一種后進先出的數(shù)據(jù)結(jié)構(gòu)乌妒。調(diào)用棧主要用于記錄代碼執(zhí)行位置汹想、當前執(zhí)行環(huán)境。

function baz() { 
    // 當前調(diào)用棧是:baz 
    // 因此撤蚊,當前調(diào)用位置是全局作用域 
    console.log( "baz" ); 
    bar(); // <-- bar 的調(diào)用位置 
} 

function bar() { 
    // 當前調(diào)用棧是 baz -> bar 
    // 因此古掏,當前調(diào)用位置在 baz 中 
    console.log( "bar" ); 
    foo(); // <-- foo 的調(diào)用位置 
} 

function foo() { 
    // 當前調(diào)用棧是 baz -> bar -> foo 
    // 因此,當前調(diào)用位置在 bar 中 
    console.log( "foo" ); 
} 

baz(); // <-- baz 的調(diào)用位置

(二)綁定規(guī)則

  1. 默認綁定獨立函數(shù)調(diào)用——無法應用其他規(guī)則時的默認規(guī)則)侦啸,this指向全局對象window槽唾。
function foo() {
    console.log( this.a ); // this指向全局對象
}
var a = 2;
foo(); // 2

對于默認綁定來說,決定this綁定對象的并不是調(diào)用位置是否處于嚴格模式匹中,而是函數(shù)體是否處于嚴格模式夏漱。
如果函數(shù)體處于嚴格模式,this會被綁定到undefined顶捷,否則this會被綁定到全局對象挂绰。

function foo() {  
    console.log( this.a ); 
} 

var a = 2; 
(function(){  
    "use strict"; 
    foo(); // 2,嚴格模式下與 foo() 的調(diào)用位置無關
})();
  1. 隱式綁定

在一個函數(shù)上下文中服赎,this由調(diào)用者提供葵蒂,由調(diào)用函數(shù)的方式來決定。如果調(diào)用者函數(shù)重虑,被某一個對象所擁有践付,那么該函數(shù)在調(diào)用時,內(nèi)部的this指向該對象缺厉。

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2
// 調(diào)用位置會使用 obj 上下文來引用函數(shù)永高,
// 因此你可以說函數(shù)被調(diào)用時 obj 對象“擁有”或者“包含”它
// 所以此時的 this 指向調(diào)用 foo 函數(shù)的 obj 對象

對象屬性引用鏈中只有最頂層或者說最后一層會影響調(diào)用位置,也就是說this指向最終調(diào)用函數(shù)的對象提针。舉例來說:

function foo() {
    console.log( this.a );
}

var obj2 = {
    a: 42,
    foo: foo
};

var obj1 = {
    a: 2,
    obj2: obj2
};

obj1.obj2.foo(); // 42命爬,此時的 this 指向 obj2 對象

隱式丟失

function foo() {
    console.log( this.a );
}

var obj = {
    a: 2,
    foo: foo
};

var bar = obj.foo; // 函數(shù)別名!函數(shù)的引用而不是函數(shù)的調(diào)用7薄K峭稹!
var a = "oops, global"; // a 是全局對象的屬性
bar(); // "oops, global"

// 雖然 bar 是 obj.foo 的一個引用嗜价,但是實際上艇抠,它引用的是foo 函數(shù)本身,
// 因此此時的 bar() 其實是一個不帶任何修飾的函數(shù)調(diào)用久锥,因此應用了默認綁定
  1. 顯式綁定call(..)apply(..) 方法)

call()apply()方法家淤,它們的第一個參數(shù)是一個對象,它們會把這個對象綁定到this瑟由,接著在調(diào)用函數(shù)時指定這個this媒鼓。

function foo() {
    console.log( this.a );
}
var obj = {
    a:2
};
foo.call( obj ); // 2
// 在調(diào)用 foo 時強制把它的 this 綁定到 obj 上
  1. new綁定

JavaScript 中,構(gòu)造函數(shù)只是一些使用new操作符時被調(diào)用的函數(shù)错妖。它們并不會屬于某個類绿鸣,也不會實例化一個類。實際上暂氯,它們甚至都不能說是一種特殊的函數(shù)類型潮模,它們只是被new操作符調(diào)用的普通函數(shù)而已

使用new 來調(diào)用函數(shù)痴施,或者說發(fā)生構(gòu)造函數(shù)調(diào)用時擎厢,會自動執(zhí)行下面的操作:

  1. 創(chuàng)建(或者說構(gòu)造)一個全新的對象;
  2. 將構(gòu)造函數(shù)的作用域賦給新對象(因此this就指向了這個新對象)辣吃;
  3. 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性动遭、方法等);
  4. 如果函數(shù)沒有返回其他對象神得,那么new表達式中的函數(shù)調(diào)用會自動返回這個新對象厘惦。
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上

(三)判斷 this

this的判斷可以按照下面的優(yōu)先級順序來判斷函數(shù)在某個調(diào)用位置應用的是哪條規(guī)則:

  1. 函數(shù)是否在new 中調(diào)用(new 綁定)兔甘?
    如果是的話丹皱,this 綁定的是新創(chuàng)建的對象溯革。
var bar = new foo();
  1. 函數(shù)是否通過callapply(顯式綁定)或者硬綁定調(diào)用羡玛?如果是的話,this綁定的是指定的對象宗苍。
var bar = foo.call(obj2);
  1. 函數(shù)是否在某個上下文對象中調(diào)用(隱式綁定)稼稿?如果是的話,this綁定的是那個上下文對象讳窟。
var bar = obj1.foo();
  1. 如果都不是的話让歼,使用默認綁定。如果在嚴格模式下挪钓,就綁定到undefined是越,否則綁定到全局對象
var bar = foo();

(四)綁定例外

  1. 被忽略的this

null 或者undefined作為this的綁定對象傳入call碌上、apply或者bind倚评,這些值在調(diào)用時會被忽略,實際應用的是默認綁定規(guī)則馏予。

function foo() {  
    console.log( this.a ); 
} 

var a = 2; 
foo.call( null ); // 2
  1. 間接引用

間接引用最容易在賦值時發(fā)生天梧;間接引用時,調(diào)用這個函數(shù)會應用默認綁定規(guī)則霞丧。

function foo() {
    console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2
// 賦值表達式 p.foo=o.foo 的返回值是目標函數(shù)的引用呢岗,也就是 foo 函數(shù)的引用
// 因此調(diào)用位置是 foo() 而不是 p.foo() 或者 o.foo()

(五)this詞法

箭頭函數(shù)并不是使用function關鍵字定義的,而是使用被稱為“胖箭頭”的操作符 => 定 義的。

箭頭函數(shù)不使用 this的四種標準規(guī)則后豫,而是根據(jù)外層(函數(shù)或者全局)作用域來決定 this悉尾。

function foo() { 
    // 返回一個箭頭函數(shù)  
    return (a) => { 
        //this 繼承自 foo() 
        console.log( this.a );  
    }; 
} 
var obj1 = {  
    a:2 
}; 
var obj2 = {  
    a:3 
};

var bar = foo.call( obj1 ); 
bar.call( obj2 ); // 2, 不是 3 !

// foo() 內(nèi)部創(chuàng)建的箭頭函數(shù)會捕獲調(diào)用時 foo() 的 this挫酿。
// 由于 foo() 的 this 綁定到 obj1构眯, bar(引用箭頭函數(shù))的 this 也會綁定到 obj1,
// this一旦被確定早龟,就不可更改惫霸,所以箭頭函數(shù)的綁定無法被修改。(new 也不行4械堋)
this的指向

小結(jié)

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

  1. new 調(diào)用硅卢?綁定到新創(chuàng)建的對象
  2. call 或者 apply(或者 bind)調(diào)用妖混?綁定到指定的對象老赤。
  3. 由上下文對象調(diào)用?綁定到那個上下文對象制市。
  4. 默認:在嚴格模式下綁定到 undefined抬旺,否則綁定到全局對象

ES6 中的箭頭函數(shù)并不會使用四條標準的綁定規(guī)則祥楣,而是根據(jù)當前的詞法作用域來決定 this开财,具體來說,箭頭函數(shù)會繼承外層函數(shù)調(diào)用的 this 綁定(無論 this 綁定到什么)误褪。這其實和 ES6 之前代碼中的 self = this 機制一樣责鳍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市兽间,隨后出現(xiàn)的幾起案子历葛,更是在濱河造成了極大的恐慌,老刑警劉巖嘀略,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恤溶,死亡現(xiàn)場離奇詭異,居然都是意外死亡帜羊,警方通過查閱死者的電腦和手機咒程,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讼育,“玉大人帐姻,你說我怎么就攤上這事稠集。” “怎么了饥瓷?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵剥纷,是天一觀的道長。 經(jīng)常有香客問我扛伍,道長筷畦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任刺洒,我火速辦了婚禮,結(jié)果婚禮上吼砂,老公的妹妹穿的比我還像新娘逆航。我一直安慰自己,他們只是感情好渔肩,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布因俐。 她就那樣靜靜地躺著,像睡著了一般周偎。 火紅的嫁衣襯著肌膚如雪抹剩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天蓉坎,我揣著相機與錄音澳眷,去河邊找鬼。 笑死蛉艾,一個胖子當著我的面吹牛钳踊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播勿侯,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼拓瞪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了助琐?” 一聲冷哼從身側(cè)響起祭埂,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兵钮,沒想到半個月后蛆橡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡矢空,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年航罗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屁药。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡粥血,死狀恐怖柏锄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情复亏,我是刑警寧澤趾娃,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站缔御,受9級特大地震影響抬闷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耕突,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一笤成、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧眷茁,春花似錦炕泳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至登刺,卻和暖如春籽腕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纸俭。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工皇耗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掉蔬。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓廊宪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親女轿。 傳聞我的和親對象是個殘疾皇子箭启,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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