this的4種綁定規(guī)則

每個(gè)函數(shù)的this是在調(diào)用時(shí)被綁定的或辖,完全取決于函數(shù)的調(diào)用位置(也就是函數(shù)的調(diào)用方法)

調(diào)用位置

在理解this的綁定過程之前塞弊,首先要理解調(diào)用位置:調(diào)用位置就是函數(shù)在代碼中被調(diào)用的位置(而不是聲明的位置)敛腌。
最重要的是要分析調(diào)用棧(就是為了到達(dá)當(dāng)前執(zhí)行位置所調(diào)用的所有函數(shù))封拧。我們關(guān)心的調(diào)用位置就在當(dāng)前正在執(zhí)行的函數(shù)的前一個(gè)調(diào)用中复唤。
下面我們來看看到底什么是調(diào)用棧和調(diào)用位置:

    function baz() {
        // 當(dāng)前調(diào)用棧是:baz
        // 因此,當(dāng)前調(diào)用位置是全局作用域

        console.log("baz");
        bar(); // bar的調(diào)用位置
    }

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

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

    baz()

注意我們是如何(從調(diào)用棧中)分析出真正的調(diào)用位置的缝彬,因此它決定了this的綁定萌焰。

綁定規(guī)則

我們來看看在函數(shù)的執(zhí)行過程中調(diào)用位置如何決定this的綁定對象。

1.默認(rèn)綁定

首先要介紹的是最常用的函數(shù)調(diào)用方式:獨(dú)立函數(shù)調(diào)用谷浅“歉可以把這條規(guī)則看作是無法應(yīng)用其他規(guī)則時(shí)的默認(rèn)時(shí)的規(guī)則。

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

當(dāng)調(diào)用foo()一疯,this.a被解析成了全局變量a撼玄。為什么?因?yàn)樵诒纠卸昭瘮?shù)調(diào)用時(shí)應(yīng)用了this的默認(rèn)綁定掌猛,因此this指向全局對象。
那么怎么知道這里應(yīng)用了默認(rèn)綁定呢眉睹?可以通過分析調(diào)用位置來看看foo()是如何調(diào)用的荔茬。在代碼中,foo()是直接不帶任何修飾的函數(shù)引用進(jìn)行調(diào)用的竹海,因此只能使用默認(rèn)綁定慕蔚,無法應(yīng)用其他規(guī)則。
如果使用嚴(yán)格模式(strict mode)斋配。那么全局對象將無法使用默認(rèn)綁定孔飒,因此this會(huì)板頂?shù)絬ndefined:

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

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

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

2. 隱式綁定

另一條需要考慮的規(guī)則是調(diào)用位置是否有上下文對象,或者說是否被某個(gè)對象擁有或者包含

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

首先需要注意的是foo()的聲明方式惦积,及其之后是如何被當(dāng)做引用屬性添加到obj中的接校,但是無論是直接在obj中定義還是先定義再添加為引用屬性,這個(gè)函數(shù)嚴(yán)格來說都不屬于obj對象狮崩。
然而蛛勉,調(diào)用位置會(huì)使用obj上下文來引用函數(shù),因此你可以說函數(shù)被調(diào)用時(shí)obj對象“擁有”或者“包含”它睦柴。
foo()被調(diào)用時(shí)诽凌,它的落腳點(diǎn)確實(shí)指向obj對象。當(dāng)函數(shù)引用有上下文對象時(shí)坦敌,隱式綁定規(guī)則會(huì)把函數(shù)調(diào)用中的this綁定到這個(gè)上下文對象侣诵。因?yàn)檎{(diào)用foo()時(shí)this被綁定到obj,因此this.a 和obj.a 是一樣的狱窘。
對象屬性引用鏈中只有最頂層或者說最后一層會(huì)影響調(diào)用位置杜顺。

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

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

    var obj2 = {
        a: 3,
        obj: obj
    }

    obj2.obj.foo() // 2

綁定丟失
一個(gè)最常見的this綁定問題就是被隱式綁定的函數(shù)會(huì)丟失綁定對象,也就是說他會(huì)應(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"

    bar() // "oops, global"

雖然bar是obj.foo的一個(gè)引用搭儒,但是實(shí)際上穷当,它引用的是foo函數(shù)本身,因此此時(shí)的bar()其實(shí)是一個(gè)不帶任務(wù)修飾的函數(shù)調(diào)用淹禾,因此引用了默認(rèn)綁定馁菜。
一種更微妙,更常見并且更出乎意料的情況發(fā)生在傳入回調(diào)函數(shù)中:

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

    function doFoo(fn) {
        // fn 引用的是foo
        fn()
    }

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

    var a = "oops, global"

    doFoo(obj.foo) //"oops, global"

參數(shù)傳遞其實(shí)就是一種隱式賦值铃岔,因此我們傳入函數(shù)時(shí)也會(huì)被隱式賦值汪疮,所以結(jié)果和上個(gè)例子一樣。

如果把函數(shù)傳入內(nèi)置的函數(shù)而不是傳入你自己聲明的函數(shù)呢德撬?

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

    function doFoo(fn) {
        // fn 引用的是foo
        fn()
    }

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

    var a = "oops, global"

    setTimeout(obj.foo, 1000) // "oops, global"

JavaScript環(huán)境中內(nèi)置的setTimeout()函數(shù)實(shí)現(xiàn)和下面?zhèn)未a類似:

function setTimeout(fn, delay){
    // 等待delay毫秒
    fn(); // 調(diào)用位置
}

就像我們看到的那樣铲咨,回調(diào)函數(shù)丟失this綁定是非常常見的。除此之外蜓洪,還有一種情況的this的行為會(huì)出乎意料:調(diào)用回調(diào)函數(shù)的函數(shù)可能會(huì)修改this.在一些流行的JavaScript庫中事件處理器常會(huì)把回調(diào)函數(shù)的thsi強(qiáng)制綁定到觸發(fā)事件的DOM元素上纤勒。

3.顯式綁定

就像我們剛才看到的那樣,在分析隱式綁定時(shí)隆檀,我們必須在一個(gè)對象內(nèi)部包含一個(gè)指向函數(shù)的屬性摇天,并通過這個(gè)屬性間接引用函數(shù)粹湃,從而把this間接隱式的綁定到這個(gè)對象上。
JavaScript中所有的函數(shù)都有一些有用的特性(這和它們的[[原型]]有關(guān))泉坐,可以用來解決這個(gè)問題为鳄。具體來說可以使用函數(shù)的call(...)和apply(...)方法。嚴(yán)格來說腕让,JavaScript的宿主環(huán)境有時(shí)會(huì)提供一些非常特殊的函數(shù)孤钦,它們并沒有這兩個(gè)方法。但是這樣的函數(shù)是非常罕見的纯丸,JavaScript提供的絕大多數(shù)函數(shù)以及你自己創(chuàng)建的所有函數(shù)都可以使用call(...)和apply(...)方法偏形。
第一個(gè)參數(shù)是一個(gè)對象,它們會(huì)把這個(gè)對象綁定到this觉鼻,接著調(diào)用函數(shù)時(shí)指定這個(gè)this俊扭。因?yàn)槟憧梢灾苯又付╰his的綁定對象,所以我們稱為顯式綁定坠陈。

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

var obj = {
    a: 2
}

foo.call(obj) // 2

通過foo.call(...)萨惑,我們可以在調(diào)用foo時(shí)強(qiáng)制把它的this綁定到obj上。
顯式綁定仍然無法解決我們之前的丟失 綁定問題仇矾。
硬綁定
顯式綁定的一種變種可以解決這個(gè)問題

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

    var obj = {
        a: 2
    }
    
    var bar = function () {
        foo.call(obj)
    }
    
    bar(); // 2
    
    setTimeout(bar, 100) // 2
    
    //應(yīng)邦定的bar不可能在修改它的this
    bar.call(window) // 2

首先我們創(chuàng)建了函數(shù)bar();并且在它的內(nèi)部手動(dòng)調(diào)用了foo.call(obj),因此就強(qiáng)制把foo的this綁定到了obj上庸蔼。無論之后如何調(diào)用函數(shù)的bar,它總會(huì)手動(dòng)在obj上調(diào)用foo若未。這種板頂是一種顯試的強(qiáng)制綁定朱嘴,稱之為硬綁定。
硬綁定的典型應(yīng)用場景就是創(chuàng)建一個(gè)包裹函數(shù)粗合,傳入所有的參數(shù)并返回接受到的所有值:

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

    var obj = {
        a: 2
    }

    var bar = function () {
        return foo.apply(obj, arguments)
    }

    var b = bar(3) // 2 3
    console.log(b); // 5

另一種使用方法是創(chuàng)建一個(gè)i可以重復(fù)使用的輔助函數(shù):

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

    function bind(fn, obj) {
        return function () {
            return fn.apply(obj, arguments)
        }
    }

    var obj = {
        a: 2
    }

    var bar = bind(foo, obj)

    var b = bar(3) // 2 3

    console.log(b);// 5

由于硬綁定是一種非常常用的模式,所以在ES5中提供了內(nèi)置的方法Function.prototype.bind乌昔,它的用法如下:

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

    var obj = {
        a: 2
    }

    var bar = foo.bind(obj);

    var b = bar(3) // 2 3

    console.log(b); // 5

bind會(huì)返回一個(gè)硬編碼的新函數(shù)隙疚,它會(huì)把參數(shù)設(shè)置為this的上下文并調(diào)用原始函數(shù)。

API調(diào)用的上下文
第三方庫的許多函數(shù)磕道,以及JavaScript語言和宿主環(huán)境中許多新的內(nèi)置函數(shù)供屉,都提供了一個(gè)可選的參數(shù),通常被稱為“上下文”(context)溺蕉,作用和bind(...)一樣伶丐,確保你的回調(diào)函數(shù)使用指定的this。

4. new綁定

在傳統(tǒng)面向類的語言中疯特,“構(gòu)造函數(shù)”是類中的一些特殊方法哗魂,使用new初始化類時(shí)會(huì)調(diào)用類中的構(gòu)造函數(shù)。通常的形式是這樣的:

something = new MyClass(...)

javascript也有一個(gè)new操作符,使用方法看起來也和那些面向類的語言一樣。絕大多數(shù)開發(fā)者都認(rèn)為javascript中的new機(jī)制和其他語言一樣凳厢。然后蜀踏,機(jī)制完全不同针余。
首先我們重新定義一下javascript中的構(gòu)造函數(shù)姥宝。在javascript中膳沽,構(gòu)造函數(shù)只是一些使用new操作符時(shí)被調(diào)用的函數(shù)分苇。它們并不會(huì)屬于某個(gè)類崔列,也不會(huì)實(shí)例化某個(gè)類梢褐。實(shí)際上,它們甚至都不能說是一種特殊的函數(shù)類型赵讯,它們只是被new操作符調(diào)用的普通函數(shù)而已盈咳。
使用new來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)時(shí)瘦癌,會(huì)自動(dòng)執(zhí)行以下操作猪贪。

  • 1.創(chuàng)建(或者說構(gòu)造)一個(gè)全新的對象。
  • 2.這個(gè)新對象會(huì)被執(zhí)行[[原型]]連接讯私。
  • 3.這個(gè)新對象會(huì)綁定到函數(shù)調(diào)用的this热押。
  • 4.如果函數(shù)沒有返回其他對象,那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對象斤寇。
function foo(a) {
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a) // 2

使用new來調(diào)用foo(...)時(shí)桶癣,我們會(huì)構(gòu)造一個(gè)新對象并把它綁定到foo(...)調(diào)用中的this上。new是最后一種可以影響函數(shù)調(diào)用時(shí)this綁定行為的方法娘锁,我們稱之為new綁定牙寞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市莫秆,隨后出現(xiàn)的幾起案子间雀,更是在濱河造成了極大的恐慌,老刑警劉巖镊屎,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惹挟,死亡現(xiàn)場離奇詭異,居然都是意外死亡缝驳,警方通過查閱死者的電腦和手機(jī)连锯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來用狱,“玉大人运怖,你說我怎么就攤上這事∠囊粒” “怎么了摇展?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長署海。 經(jīng)常有香客問我吗购,道長医男,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任捻勉,我火速辦了婚禮镀梭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘踱启。我一直安慰自己报账,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布埠偿。 她就那樣靜靜地躺著透罢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冠蒋。 梳的紋絲不亂的頭發(fā)上羽圃,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機(jī)與錄音抖剿,去河邊找鬼朽寞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛斩郎,可吹牛的內(nèi)容都是我干的脑融。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缩宜,長吁一口氣:“原來是場噩夢啊……” “哼肘迎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锻煌,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤妓布,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后宋梧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秋茫,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年乃秀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片圆兵。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跺讯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出殉农,到底是詐尸還是另有隱情刀脏,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布超凳,位于F島的核電站愈污,受9級特大地震影響耀态,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜暂雹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一首装、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧杭跪,春花似錦仙逻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姑廉,卻和暖如春缺亮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桥言。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工萌踱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人限书。 一個(gè)月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓虫蝶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親倦西。 傳聞我的和親對象是個(gè)殘疾皇子能真,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

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

  • 思考 這是個(gè)很有意思的關(guān)鍵字,我們經(jīng)常見到它扰柠,有時(shí)候卻不怎么喜歡它粉铐,在JavaScript中,它有太多的魔力卤档,太深...
    y小賢閱讀 652評論 0 0
  • this到底是什么蝙泼?this是在運(yùn)行的進(jìn)行綁定的,并不是編寫時(shí)綁定的劝枣。他的上下文取決于函數(shù)調(diào)用的各種環(huán)境汤踏。this...
    小烏龜變王八閱讀 332評論 0 1
  • 調(diào)用位置 通常而言,調(diào)用位置就是函數(shù)被調(diào)用的位置 注意調(diào)用棧 綁定規(guī)則 默認(rèn)綁定獨(dú)立函數(shù)調(diào)用是最常用的函數(shù)調(diào)用類型...
    nymlc閱讀 151評論 0 0
  • 在前端中this的作用機(jī)制可以說和原型舔腾,作用域等一樣非常重要溪胶。本文在此就《你不知道的Javascript》(上)中...
    咖啡色的自由閱讀 388評論 3 0
  • 深夜的一個(gè)哭訴電話哗脖,持續(xù)了快一個(gè)小時(shí),讓我的睡意全無。我心疼我最愛的人才避,那個(gè)在我眼里強(qiáng)勢橱夭、能干、優(yōu)秀的人桑逝。她活的積...
    YvetteYvette閱讀 493評論 0 2