this綁定規(guī)則優(yōu)先級

現(xiàn)在了解了函數(shù)調(diào)用中this綁定的四條規(guī)則,需要做的是找到函數(shù)的調(diào)用位置并判斷應(yīng)用了哪條規(guī)則。如果調(diào)用位置應(yīng)用多條規(guī)則,就必須給這些規(guī)則設(shè)定優(yōu)先級。
毫無疑問,默認綁定是四條規(guī)則中最低的,可以暫時先不考慮。
測試下隱式綁定和顯式綁定優(yōu)先級:

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

    var obj1 = {
        a: 1,
        foo: foo
    }

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

    obj1.foo() // 1
    obj2.foo() // 2

    obj1.foo.call(obj2) // 2
    obj2.foo.call(obj1) // 1

顯式綁定優(yōu)先級更高,也就是說在判斷時應(yīng)當(dāng)先考慮是否可以應(yīng)用顯式綁定。
測試下new綁定和隱式綁定優(yōu)先級:

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

    var obj1 = {
        foo: foo
    }

    var obj2 = {}

    obj1.foo(2)
    console.log(obj1.a) // 2

    obj1.foo.call(obj2, 3)
    console.log(obj2.a) // 3

    var bar = new obj1.foo(4) // new 和 call/apply 無法一起使用,因此無法通過new foo.call(obj1)來直接測試
    console.log(obj1.a) // 2

    console.log(bar.a) // 4

new綁定比隱式綁定優(yōu)先級高。接下來就是比較new綁定和顯式綁定?
在看代碼之前,先回憶下硬綁定是如何工作的。Function.prototype.bind(...)會創(chuàng)建一個新的包裝函數(shù),這個函數(shù)會忽略當(dāng)前的this綁定(無論綁定的對象時什么),并把提供的對象綁定到this上。
這樣看起來硬綁定(也是顯式綁定的一種)似乎b比new綁定的優(yōu)先級更高。

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

    var obj1 = {}

    var bar = foo.bind(obj1)

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

    var baz = new bar(3)

    console.log(obj1.a) // 2
    console.log(baz.a) // 3

bar被硬綁定到了obj1上,但是new bar(3)并沒有向我們預(yù)計的那樣把obj1.a的值修改為3。相反,new修改了硬綁定(到obj1的)調(diào)用bar(...)中的this。因此使用了new綁定,我們得到了一個名字為baz的新對象,并且baz.a的值是3。

判斷this

如果要判斷一個運行中函數(shù)this的綁定,那么就需要找到這個函數(shù)的直接調(diào)用位置。找到之后按照以下規(guī)則判斷this綁定的對象。

  • 1.函數(shù)是否在new中調(diào)用(new綁定)?如果是this綁定的是新創(chuàng)建的對象
    var bar = new foo()
  • 2.函數(shù)是否通過call、apply(顯式綁定)或者硬綁定調(diào)用?如果是this綁定的是指定的對象
    var bar = foo.call(obj1)
  • 3.函數(shù)是否在某個上下文對象中調(diào)用(隱式綁定)?如果是this綁定的是那個上下文對象
    var bar = obj1.foo()
  • 4.如果都不是,使用默認綁定。如果在嚴格模式下,就綁定到了undefined,否則綁定到了全局對象。
    var bar = foo()
綁定例外

1.被忽略的this
如果你把null或者undefined作為this的綁定對象傳入到call/apply/bind,這些值在調(diào)用時會被忽略,實際應(yīng)用的會是默認綁定

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

什么情況下你會傳入null?
一種非常常見的做法是使用apply(...)來“展開”一個數(shù)組,并當(dāng)做參數(shù)傳入一個函數(shù)。類似的,bind(...)可以對參數(shù)進行kelihua柯里化(預(yù)先設(shè)置一些參數(shù)),非常有用:

    function foo(a, b) {
        console.log("a: " + a + ", b:" + b)
    }

    // 把數(shù)組展開成參數(shù)
    foo.apply(null, [2, 3]) // a: 2, b:3
    // 使用bind(...)進行柯里化
    var baz = foo.bind(null, 4)
    baz(5) //a: 4, b:5

這兩種方法都需要傳入一個參數(shù)當(dāng)做this的綁定對象。如果函數(shù)不關(guān)心this的話,你仍然需要傳入一個占位值,這時null就是個不錯的選擇。
使用null帶來的副作用是,如果某個函數(shù)確實使用了this,那么默認綁定會把this綁定到全局對象中。(可以使用Object.create(null)來代替null更安全)

2.間接引用
需要注意的是,可能創(chuàng)建一個函數(shù)的間接引用,這種情況下會應(yīng)用默認綁定

    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)(); // 4

賦值表達式p.foo = o.foo的返回值是目標(biāo)函數(shù)的引用,因此調(diào)用位置是foo()而不是p.foo()或者o.foo(),所以會應(yīng)用默認綁定。
注意:對于默認綁定來說,決定this綁定對象的并不是調(diào)用位置是否處于嚴格模式,而是函數(shù)體是否處于嚴格模式,嚴格模式下this綁定到undefined,否則this綁定到全局對象

3.軟綁定

this詞法

ES6中介紹了一種無法使用這些規(guī)則的特殊函數(shù):箭頭函數(shù)。
箭頭函數(shù)并不是使用function定義的,箭頭函數(shù)不適用這四種標(biāo)準(zhǔn)規(guī)則,而是根據(jù)外層作用域來決定this。(箭頭函數(shù)會繼承外層函數(shù)調(diào)用的this綁定)

    function foo() {
        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

foo內(nèi)部創(chuàng)建的箭頭函數(shù)會捕獲調(diào)用foo()的this,由于foo()的this綁定到的是obj1,bar(引用箭頭函數(shù))的this也會綁定到obj1,箭頭函數(shù)的綁定無法修改(new也不行)
箭頭函數(shù)最常用于回調(diào)函數(shù)中,例如事件處理器或者定時器:

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

var obj = {
  a: 2
}

foo.call(obj) // 2

箭頭函數(shù)可以像bind(...)一樣確保函數(shù)的this被綁定到指定對象。此外,其重要性還體現(xiàn)在它用更常見的詞法作用域取代了傳統(tǒng)的this機制。實際上,ES6之前我們就已經(jīng)在使用一種幾乎和箭頭函數(shù)一樣模式

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

var obj = {
  a: 2
}

foo.call(obj) // 2

雖然self = this和箭頭函數(shù)看起來都可以取代bind(...),但從本質(zhì)上來說他們想取代的是this機制。
如果你經(jīng)常編寫this風(fēng)格的代碼,但絕大部分都會使用self = this或者箭頭函數(shù)來否定this機制,那你或許應(yīng)當(dāng):

  • 1.只使用詞法作用域并完全拋棄this風(fēng)格代碼
  • 2.完全采取this風(fēng)格,在必要時使用bind(...),盡量避免self=this和箭頭函數(shù)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 特別說明,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 820評論 0 1
  • 關(guān)于 this this 關(guān)鍵字是 JavaScript 中最復(fù)雜的機制之一。它是一個很特別的關(guān)鍵字,被自動定義在...
    游學(xué)者灬墨槿閱讀 634評論 1 2
  • 1.1 關(guān)于 this 當(dāng)模式越來越復(fù)雜時,顯示傳遞上下文對象會讓代碼變得越來越混亂,使用this則不會這樣。可自...
    將軍肚閱讀 728評論 0 51
  • 1、調(diào)用位置 在理解this的綁定之前,首先理解調(diào)用位置,決定this的綁定 function a() { // ...
    winerss閱讀 400評論 0 0
  • 今晚逛朋友圈突然有種逛花園的感覺,鮮花點綴,花香彌漫。小宜發(fā)來一組玫瑰花卷照片引來眾多文友圍觀,玫紅與金黃的花卷面...
    夜含閱讀 869評論 6 8

友情鏈接更多精彩內(nèi)容