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