JavaScript`魔法關(guān)鍵字`this

前言

this在JavaScript代碼中幾乎隨處可見,同時(shí),它也是JavaScript中相對(duì)復(fù)雜的機(jī)制之一熊户。前端開發(fā)者在缺乏清晰認(rèn)識(shí)的情況下,this完全就是一種魔法吭服,難以捉摸嚷堡。

全局代碼中的this

在這里一切都是簡(jiǎn)單。在全局代碼中艇棕,this始終指向全局對(duì)象本身蝌戒。

// 顯式定義全局對(duì)象的屬性
this.a = 1;
console.log(a); // 1

// 隱式定義全局變量
// 全局變量都是全局對(duì)象的屬性
b = 2;
console.log(this.b); // 2

// 顯式定義全局變量
// 全局變量都是全局對(duì)象的屬性
c = 3;
console.log(this.c); //3

函數(shù)代碼中的this

1 誤解

在函數(shù)代碼中使用this是很有趣的,人們很容易把this理解成指向函數(shù)自身沼琉,其實(shí)并不是北苟,讓我們看看下面這段代碼:

function foo(num) {
    console.log("foo: " + num);

    // 記錄foo被調(diào)用的次數(shù)
    this.count++;
}

foo.count = 0;

for (var i = 1; i < 3; i++) {
    foo(i);
}
// foo: 1
// foo: 2

// foo被調(diào)用了多少次?
console.log(foo.count); // 0 -- WTF?

console.log語(yǔ)句確實(shí)輸出了2次刺桃,但是foo.count仍然是0粹淋,顯然從字面意思來(lái)理解this是錯(cuò)誤的。
執(zhí)行foo.count = 0時(shí)瑟慈,確實(shí)向函數(shù)對(duì)象foo添加了一個(gè)屬性count桃移。但是函數(shù)內(nèi)部中this.countthis并不是指向那個(gè)函數(shù)對(duì)象。那么葛碧,this到底是指向誰(shuí)呢借杰?

2 this到底是什么

this是在運(yùn)行時(shí)進(jìn)行綁定的,并不是編寫時(shí)(定義時(shí))綁定进泼,它綁定規(guī)則取決于函數(shù)調(diào)用時(shí)的各種條件蔗衡。

2.1 默認(rèn)綁定

首先要介紹的是最常用的函數(shù)調(diào)用類型:獨(dú)立函數(shù)調(diào)用纤虽,也就是直接函數(shù)名(參數(shù))的調(diào)用方式〗实耄可以把這條規(guī)則看作是無(wú)法應(yīng)用其他規(guī)則時(shí)的默認(rèn)規(guī)則:

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

var a = 2;

foo(); // 2

你應(yīng)該注意到逼纸,聲明在全局作用域的變量var a = 2(也是全局對(duì)象的屬性)在函數(shù)foo()調(diào)用時(shí)被打印出來(lái)了!由此我們得知济蝉,默認(rèn)綁定中的this指向全局對(duì)象杰刽。

PS: 如果是在嚴(yán)格模式下,默認(rèn)綁定會(huì)是undefined王滤,而不是全局對(duì)象(不知道嚴(yán)格模式的可暫時(shí)忽略)

2.2 隱式綁定

當(dāng)函數(shù)是作為某個(gè)對(duì)象的方法(其實(shí)都是屬性)來(lái)調(diào)用時(shí)贺嫂,則屬于隱式綁定規(guī)則:

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

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

obj.foo(); // 2

這里的函數(shù)foo是被引用到對(duì)象obj的屬性中的,然后以對(duì)象.方法()來(lái)調(diào)用雁乡。此時(shí)我們可以看到this.a得到obj.a的值第喳,可以得知this指向的是obj(由哪個(gè)對(duì)象調(diào)用則指向哪個(gè)對(duì)象)了
另外注意一個(gè)隱式丟失的問(wèn)題

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

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

var bar = obj.foo; // 函數(shù)別名!

var a = "oops, global"; // a是全局對(duì)象的屬性

bar(); // "oops, global"

雖然barobj.foo的一個(gè)引用踱稍,但實(shí)際上曲饱,他們兩個(gè)都只是引用foo函數(shù)本身,因此bar()只是一個(gè)默認(rèn)綁定的調(diào)用D洹妓忍!
隱式丟失通常會(huì)在回調(diào)函數(shù)中見到注暗,可以用下面說(shuō)到的顯示綁定解決诞帐!

2.3 顯式綁定

當(dāng)函數(shù)是通過(guò)callapply方法進(jìn)行調(diào)用的時(shí)候尉尾,我們稱之為顯示綁定:

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

var obj = {
    a: 2
};

foo.call(obj); // 2
foo.apply(obj); // 2

通過(guò)foo.call(obj)foo.apply(obj)深胳,我們就已經(jīng)把foo中的this指向了obj

this的綁定角度來(lái)說(shuō)择浊,callapply是一樣的寨闹,它們的區(qū)別體現(xiàn)在其他參數(shù)上础嫡,請(qǐng)自行查看api

另外ES5中提供了bind方法氛谜,該方法只是用來(lái)綁定函數(shù)的this指向掏觉,不會(huì)執(zhí)行函數(shù):

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

var obj = { 
    a: 2
};

var bar = foo.bind(obj);

bar(3); // 2 3 

我們可以看到foo.bind(obj)只是返回了一個(gè)綁定了this的函數(shù),因此調(diào)用bar(3)時(shí)已經(jīng)不是默認(rèn)綁定了值漫,我們也可以利用這個(gè)方法解決上面說(shuō)到的隱式丟失問(wèn)題:

// 隱式丟失原代碼
    function foo() { 
        console.log(this.a);
    }

    var obj = { 
        a: 'obj',
        foo: foo
    };
    var a = 'global';

    setTimeout(obj.foo, 100); // "global"

// 用bind解決隱式丟失
    function foo() { 
        console.log(this.a);
    }

    var obj = { 
        a: 'obj',
        foo: foo
    };
    var a = 'global';

    setTimeout(obj.foo.bind(obj), 100); // "obj"

我們可以看到澳腹,原代碼中把obj.foo作為回調(diào)參數(shù)傳進(jìn)setTimeout中,this的指向就丟失了杨何,為什么呢酱塔?我們說(shuō)過(guò)this是根據(jù)調(diào)用的方式來(lái)確定指向的,這里只是把函數(shù)引用傳進(jìn)去危虱,還沒(méi)有調(diào)用羊娃,內(nèi)部調(diào)用的時(shí)候可能就是應(yīng)用了默認(rèn)綁定規(guī)則,然后this就指向了全局對(duì)象埃跷,setTimeout的偽代碼如下:

function setTimeout (fn, delay) {
    // 等待delay毫秒
    fn(); // 調(diào)用回調(diào)函數(shù)蕊玷,可以看到這里是獨(dú)立調(diào)用函數(shù)
}

而我們把用了bind進(jìn)行綁定返回的函數(shù)作為回調(diào)參數(shù)邮利,就解決了this丟失指向的問(wèn)題!

2.4 new綁定

這是最后一條綁定規(guī)則垃帅,使用new來(lái)調(diào)用構(gòu)造函數(shù)時(shí)延届,就是new綁定規(guī)則:

function foo(a) { 
    this.a = a;
} 

var bar = new foo(2);

console.log( bar.a ); // 2

使用new來(lái)調(diào)用函數(shù),會(huì)執(zhí)行下面的操作:

  1. 創(chuàng)建(構(gòu)造)一個(gè)新的對(duì)象
  2. 對(duì)這個(gè)對(duì)象進(jìn)行原型鏈接bar.__proto__ = foo.prototype(這不是重點(diǎn))
  3. 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的this,也就是this.a = athis指向新對(duì)象
  4. 如果函數(shù)沒(méi)有返回其他對(duì)象贸诚,那么就返回這個(gè)新對(duì)象

總的來(lái)說(shuō)祷愉,使用new來(lái)調(diào)用foo()時(shí),函數(shù)中的this已經(jīng)指向該函數(shù)創(chuàng)建的新對(duì)象了赦颇。

2.5 優(yōu)先級(jí)

現(xiàn)在我們已經(jīng)知道了函數(shù)調(diào)用中this綁定的四條規(guī)則二鳄,你需要做的就是找到函數(shù)調(diào)用的位置進(jìn)行判斷其應(yīng)用了哪條規(guī)則,你就可以知道this的指向了媒怯。
但是订讼,如果應(yīng)用了多條規(guī)則,最終會(huì)哪個(gè)規(guī)則勝出呢扇苞?你們可以自行嘗試……這里就不多做說(shuō)明欺殿,直接給出答案。

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

2.6 ES6中的箭頭函數(shù)

作為ES6中的新姿勢(shì)鳖敷,箭頭函數(shù)的this是凌駕于以上規(guī)則之外的脖苏,是根據(jù)外層作用域來(lái)決定this:

function foo() {
    setTimeout(() => {
        // 這里的this在此法上繼承自foo()
        console.log(this.a);
    }, 100);
}

var obj = {
    a: 2
};

foo.call(obj); // 2

我們通過(guò)顯式綁定foo后,內(nèi)部的箭頭函數(shù)的this也繼承了過(guò)來(lái)定踱。其實(shí)這只是一個(gè)語(yǔ)法糖棍潘,只是用了一個(gè)私有變量來(lái)存儲(chǔ)this,轉(zhuǎn)換了一下代碼:

function foo() {
    var self = this; // 定義了一個(gè)私有變量來(lái)存儲(chǔ)foo的`this`
    setTimeout(function() {
        console.log(self.a);
    }, 100);
}

var obj = {
    a: 2
};

foo.call(obj); // 2

不過(guò)寫法上來(lái)說(shuō)還是挺帥氣的~哈哈崖媚!

結(jié)語(yǔ)

總的來(lái)說(shuō)亦歉,只要找到調(diào)用位置,分析其規(guī)則即可正確找到this畅哑。
第一次寫博文肴楷,可能寫的有點(diǎn)亂,請(qǐng)拿起磚頭往死里拍……

知識(shí)吸取自《你不知道的JavaScript》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荠呐,一起剝皮案震驚了整個(gè)濱河市赛蔫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泥张,老刑警劉巖呵恢,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異圾结,居然都是意外死亡瑰剃,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門筝野,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)晌姚,“玉大人粤剧,你說(shuō)我怎么就攤上這事』舆耄” “怎么了抵恋?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宝磨。 經(jīng)常有香客問(wèn)我弧关,道長(zhǎng),這世上最難降的妖魔是什么唤锉? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任世囊,我火速辦了婚禮,結(jié)果婚禮上窿祥,老公的妹妹穿的比我還像新娘株憾。我一直安慰自己,他們只是感情好晒衩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布嗤瞎。 她就那樣靜靜地躺著,像睡著了一般听系。 火紅的嫁衣襯著肌膚如雪贝奇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天靠胜,我揣著相機(jī)與錄音掉瞳,去河邊找鬼。 笑死髓帽,一個(gè)胖子當(dāng)著我的面吹牛菠赚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播郑藏,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瘩欺!你這毒婦竟也來(lái)了必盖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤俱饿,失蹤者是張志新(化名)和其女友劉穎歌粥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拍埠,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡失驶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了枣购。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嬉探。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡擦耀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涩堤,到底是詐尸還是另有隱情眷蜓,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布胎围,位于F島的核電站吁系,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏白魂。R本人自食惡果不足惜汽纤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望福荸。 院中可真熱鬧冒版,春花似錦、人聲如沸逞姿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滞造。三九已至续室,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谒养,已是汗流浹背挺狰。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留买窟,地道東北人丰泊。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像始绍,于是被迫代替她去往敵國(guó)和親瞳购。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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