JavaScript中的this

1.函數調用棧和調用位置

在函數執行的時候,會有一個活動記錄(也叫執行上下文)來記錄函數的調用順序,這個就是函數調用棧。棧(Stack)是一種先進后出,后出先進的數據結構。

function a(){
    console.log('a')
    b()      //b的調用位置,此時調用棧 a -> b
}
function b(){
    console.log('b')
    c()     //c的調用位置,此時調用棧 a -> b -> c
}
function c(){
    console.log('c')
}
a()  //a的調用位置

以上代碼中,首先執行a函數,此時a函數被push進調用棧的棧頂;在a函數的執行過程中,調用了b函數,b函數push進棧頂;b函數的執行過程中,調用了c函數,c函數push進棧頂。在瀏覽器環境下,此時便形成了window -> a -> b -> c這樣的調用順序。在chrome中的開發者工具可以更清晰的看清楚這點。

函數的調用棧

函數的調用位置

要想理解this綁定的過程,首先要弄清楚什么是調用位置。在之前的代碼中,如果是在瀏覽器環境中,window調用了a函數,a的調用位置便是window;b函數調用了c函數,b的調用位置便是a函數;同理c函數的調用位置便是b函數。因此可以理解為棧頂正在的函數的上一個函數便是當前函數的調用位置。

2.this的四種綁定方式

this綁定的方式分為默認綁定,隱式綁定,顯式綁定和new綁定。注意以下代碼均在非嚴格模式下運行。

2.1默認綁定

function foo(){
    console.log(this.a)
    console.log(this===window)
}
var a = 10
foo()  //10  true

以上代碼中foo()是不帶任何修飾被直接調用,因此只能應用默認綁定,此時foo函數內的this對象指向了window(瀏覽器環境)。

function foo(){
    function bar(){
        console.log(this===window)
    }
    bar()  
}
foo()  //true

函數不帶任何修飾被調用,即使是在函數體內,也會應用默認綁定。上面代碼中,運行在foo函數內的bar()綁定的this指向window。

2.2 隱式綁定

在函數的調用位置需要考慮函數是否被某個上下文(或對象)所包含。

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

以上代碼中,無論函數是先聲明再引入(foo),還是在內部定義(bar),這兩種情況都會引用對應的函數。obj.foo()和obj.bar()的調用方式讓調用位置通過obj對象來引用這兩個函數,因此這兩個函數的this會隱式綁定到obj上。

隱式丟失

常見于使用回調函數的時候

function useFoo(fn){
    fn()  //調用位置
}
var obj = {
    a: 1,
    foo: function(){
        console.log(this.a)
    }
}
var a = '我是全局a'
useFoo(obj.foo)  //我是全局a

以上代碼中,雖然useFoo傳入參數的是obj.foo的方式,但其實參數傳遞是隱式賦值的方式,因此此時是將obj.foo賦值給參數fn,在調用位置上執行的fn實際上是foo(),因此應用了默認綁定,this指向全局環境。

這種情況內置的函數也不例外

function foo(){
    console.log(this.a)
}
var obj = {
    a: 2,
    foo: foo
}
var a = '全局a'
setTimeout(obj.foo,1000)  //全局a

內置的setTimeout()函數和下面的偽代碼類似

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

2.3 顯式綁定

顯式綁定是通過函數的原型方法 apply()、call()和bind()對this進行綁定。

apply(thisArg,[argsArray])

apply方法的第一個參數是一個對象,它會把函數的this綁定到這個對象上;第二個參數是傳入的參數數組。

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

上面代碼中apply方法首先將foo的this綁定到obj對象上,然后執行foo函數,這樣就實現了this的顯式綁定。

call(thisArg[, arg1[, arg2[, ...]]]):call方法和apply方法的功能是一樣的,只是call方法接受的是若干個參數列表,apply方法接受的是一個參數數組。

硬綁定

function foo(){
    console.log(this.a)
}
var obj = {
    a: 1,
    foo: foo
}
var a='全局a'
var bar = obj.foo
bar.call(obj)  //1
bar()   //全局a

以上代碼bar函數先調用call方法將this綁定到了obj對象上,但是再調用bar()函數,由于隱式丟失的原因,仍然是將this綁定到全局。可以通過硬綁定的方式來解決。

bind(thisArg[, arg1[, arg2[, ...]]]):硬綁定是bind()方法實現的,它的參數和call方法是一樣的,bind方法會返回this綁定了指定對象的原函數拷貝。

function foo(){
    console.log(this.a)
}
var obj = {
    a: 1
}
var a='全局a'
var bar = foo.bind(obj)
bar()   //1
bar.call(window)  //1,bind函數綁定的對象this對象不能再修改

上面代碼調用foo函數的bind方法將this綁定到obj上,然后返回函數給bar,此時bar的this已經綁定到obj上。注意,通過bind函數綁定的對象this對象不能再修改,因為bind函數的內部會再將this綁定到obj上。

2.4 new綁定

function Person(name){
    this.name = name
}
var bar = new Person('bar')
console.log(bar.name)  //bar

1.new Person(bar)首先會創建一個全新對象,這個對象繼承自Person.prototype,然后會將構造函數的this綁定到這個對象上;

2.如果構造函數返回了一個對象,那么它會取代第一步創建的對象,否則會返回new出來的對象(一般來說構造函數不返回任何值),因此bar是一個new創建的對象。

3.this綁定的優先級

優先級:new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定
1.如果函數使用了new關鍵字,那么this綁定到new新創建的對象

var bar = new Person()
2.函數調用了call()、apply()和bind()方法進行顯式綁定,那么this綁定到指定的對象上。
var bar = foo.call(obj)

3.函數調用位置是否被某個對象所包含,如果是則應用隱式綁定,this會綁定到那個對象。
obj.foo()

4.以上都不是,那么應用默認綁定。

4.安全的this

有時候我們使用apply()、call()和bind()方法不是想綁定某個對象的this,而只是傳入某些參數。

function foo(a,b){
    console.log(`a:${a} b:${b}`)
}
//使用apply展開數組
foo.apply(null,[1,2])   //1,2
//ES6中可以使用...運算符代替apply
foo(...[1,2])    //1,2
 
//使用bind進行柯里化
var bar = foo.bind(null,1)
bar(2)    //1,2

以上代碼傳遞了null作為第一個參數,以此來忽略this綁定。這樣的做法實際上是會應用默認綁定的,函數的this可能會綁定到全局對象上,因此這可能會存在潛在的危險。

Object.create(null)

更安全的做法是使用Object.create(null)創建一個空對象,這樣做法不會產生Object.prototype這個委托,也不會應用默認綁定。

function foo(a,b){
    console.log(this)
    console.log(`a:${a} b:${b}`)
}
var _o = Object.create(null)
//使用apply展開數組
foo.apply(_o,[1,2])   //1,2
 
//使用bind進行柯里化
var bar = foo.bind(_o,1)
bar(2)    //1,2
console.dir(_o)

5.箭頭函數的this

在ES6中的箭頭函數,其this不會應用前文的四條規則,而是取決于其外層作用域。

function foo(){
    return () => {
            //this取決于foo調用時的this
        console.log(this.a)
    }
}

var obj = {
    a: 1
}
var a = '全局a'
var bar = foo.call(obj)
bar()   //1
bar.call(window)  //1,無法修改綁定的this

foo()內部的箭頭函數會捕獲調用foo()時的this。foo的this綁定到了obj,因此bar也會綁定到obj,而且箭頭函數的綁定無法修改。

箭頭函數最常使用于回調函數中

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
}

上面init和doSomething方法使用了箭頭函數,是this綁定到了handler對象,如果使用普通函數,this.doSomething中的this則會綁定到document對象。

6.小結

要判斷一個函數綁定的this,需要找到這個函數的直接調用位置。找到之后,就按照四條綁定規則來判斷this綁定的對象。需要特別注意的是,一些調用可能會無意中應用默認綁定規則。
如果是使用箭頭函數,那么箭頭函數會繼承外層函數調用時綁定的this。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容