JavaScript中的this到底是怎樣的?

  • 2019 年 10 月 3 日
  • 筆記

this是困惑JavaScript開發者的一大‘毒瘤’,在開發過程中,但凡用到this的時候,我們都會很頭疼,那麼這個this在JavaScript中到底是怎麼樣的?身為一個前端coder,這是一個避不開的話題。

經典程式碼

下面,我們先開看一段社區裡面的經典的精華程式碼,看看它到底試圖說明什麼:

function foo() {      var a = 2;      this.bar();  }    function bar() {      console.log(this.a);  }    foo();    // ReferenceError: a is not defined

 

看了這一段程式碼,我們不由再想一下兩個問題

this指向函數自身嗎(×)?

如果this指向函數自身,foo函數中的this.bar()竟然神奇的調用成功了,這顯然是不對的。這時候如果列印this,很明顯this指向全局對象window。

this指向詞法作用域嗎(×)?

如果this指向詞法作用域,那麼為什麼bar()會引發引用錯誤?很明顯,this也並不指向詞法作用域。

我想,社區經典程式碼,大概就是為了陳述我們經常會有的這兩種錯誤的觀念,那麼,我們這時候不僅要思考了,this到底是什麼?

為什麼會出現this?

說明JavaScript為什麼要用this之前,我們先看看下面程式碼:

function printName(obj){      console.log(obj.name)  }    var obj1 = {      name: 'obj1Name'  }    var obj2 = {      name: 'obj2Name'  }    printName(obj1);    // obj1Name  printName(obj2);    // obj2Name

 

在這段程式碼中,如果要列印出某對象的name屬性,那麼就需要顯示的將對象傳遞進入方法參數,那麼如果不想要傳遞參數,有沒有什麼辦法來實現這樣的操作?

這個時候,this便應運而生了。看下面程式碼:

function printName(){      console.log(this.name)  }    var obj1 = {      name: 'obj1Name'  }    var obj2 = {      name: 'obj2Name'  }    printName.call(obj1);    // obj1Name  printName.call(obj2);    // obj2Name

 

這就是this出現的原因,this出現的宗旨是想要在函數調用的時候隱式的傳遞對象引用,想要使得我們的程式碼看起來更優雅,但是this在JavaScript的引入真的完美做到我們想要的事情了嗎?

不,並沒有,最起碼this的指向就讓大多數前端開發者頭疼不已,甚至用詞法作用域的概念去避免(self = this or 箭頭函數)。

this的特點

有上述描述,我們可以得出this的一些特點:

1、隱式傳遞對象引用,但並不能穿透作用域(經典程式碼的bar)

2、運行時調用才會產生this

3、this不指向函數自身,也不指向詞法作用域

所以,this到底是什麼?

當函數調用時,this在運行時綁定,而函數調用此時產生了執行上下文,執行上下文中包括了調用棧、參數資訊等,當然也包括了this。this正是為了在運行時綁定而存在的。

那麼,this的綁定又是怎麼樣的呢,或者說this到底是指向了什麼?

this指向(綁定)

先說JavaScript的this避坑三連:

1、當函數作為對象的方法調用的時候,函數中this指向該對象(上述call調用程式碼)。

2、當函數被正常調用(全局作用域下),this指向window。(注意:嚴格模式指向undefined)。

3、this不會有函數穿透現象(箭頭函數或者匿名函數除外)

在這裡說說第三點的函數穿透,其實和作用域穿透是相同的概念。那麼為什麼箭頭函數和匿名函數可以穿透到外層this呢?

因為箭頭函數沒有自己的執行上下文,而this又是執行上下文的一個屬性,所以,箭頭函數的this或者說在其中如果調用this的話,其實就是調用的箭頭函數外層的this。

this默認指向

要說this默認指向,只看簡單的一個普通函數調用即可,一般來說,this的默認指向是全局對象。看下面程式碼:

var obj = {      name: 'objName',      printThis: function(){          console.log(this);          function innerFunc() {              console.log(this);          }          innerFunc();      }  }    obj.printThis();

 

這段程式碼中,我們執行程式碼,會發現,innerFunc的this列印出來的竟然是全局對象window(嚴格模式下,undefined),同時,這一段程式碼也印證了,我們obj調用函數時候,外層函數的this指向的是obj,並且沒有this指向穿透現象。

顯式this

那麼有什麼辦法顯式讓對象調用this呢?(最上面的call)

看下面程式碼:

var obj = {      name: 'objName',      printThis: function(){          console.log(this);          var self = this;          function innerFunc() {              console.log(self);          }          innerFunc();      }  }    obj.printThis(); 

 

這一段程式碼,毫無疑問是對this默認指向的程式碼的修復(通過self避免,並非修改this指向,inner中this依舊指向全局對象)

箭頭函數,和self避免的方式差不多,因為箭頭函數沒有執行上下文,它內部的this相當於是外部函數的this。

所以,如果是應用self避免,和箭頭函數,我們相當於是撇開了this問題,是用詞法作用域來避免我們不熟悉的東西,如果針對到this,我們依舊感覺到有些頭疼。

那麼,這時候,就需要顯式執行this了。還是上述程式碼,做如下修改:

var obj = {      name: 'objName',      printThis: function(){          console.log(this);          function innerFunc() {              console.log(this);          }          innerFunc.call(obj);    // 顯式綁定this      }  }    obj.printThis();

 

這個時候,我們列印出來的兩個this也就完全一致,都是obj對象,因為在這裡我們用call來顯式綁定了this。

除了call,我們還可以用bind和apply來實現如此效果。

總結

this在運行時產生,率屬於函數上下文的一個屬性,this的指向不一而定,是在函數調用時根據上下文來確定,也可以利用顯式調用的call、apply、bind方式來修改this指向。

this是個不可避免的問題,當我們用箭頭函數和self避免的時候,也要思考一下this的指向,以免混淆了this和詞法作用域的概念。

我的部落格:http://www.gaoyunjiao.fun/?p=159