引言
JavaScript中this的概念令人抓狂,但不理解它很難在JavaScript的道路上前行,那麼this真的有我們想象中那麼不好理解嗎?
一、函式呼叫的形式簡單來講,函式的呼叫形式有三種:
1、當做一個普通函式呼叫,我們就叫它函式呼叫吧;
2、作為一個物件的方法呼叫,我們叫它呼叫方法;
3、作為建構函式呼叫,這種方式其實是生成了一個新物件,這個物件此時不具備任何功能。
二、傳遞給函式的引數眾所周知,定義函式的時候可以定義形參,呼叫函式的時候傳入實參,實參替換對應位置的形參。
正如您所看到的,呼叫函式時我們傳入了實參'kylin',此時形參name的值為'kylin',儘管我們手動傳入了實參,但呼叫過程中,JS引擎還幫我們默默隱式地傳入了兩個引數,而在函式內這兩個引數用this和arguments表示了,我們暫時不考慮arguments,只關注this。
形參name被我們傳入的實參替代了,換句話說,函式內的name代表實參'kylin',那麼被隱式傳遞的this代表的是什麼呢?先記住這個問題。
三、誰是我的呼叫環境?上面的例子中,我們在全域性呼叫了say函式,那麼我們可以說,全域性環境(window)是函式的呼叫環境。有些書可能會叫做“全域性環境作為呼叫函式的上下文”,意思是一樣的。
如果作為物件的方法被呼叫,那麼這個物件的環境就是函式的呼叫環境。
四、this是什麼?JavaScript是在執行的時候才能確定函式的執行環境,我們在寫程式碼時,有時會需要訪問呼叫環境,此時我們必須要有一個能表示未來呼叫環境的東西去表示執行時的呼叫環境。
這就好比一套PPT,裡面有一個固定的模板用於顯示不同會議的主題,哪個部門開會,這套PPT的主題就顯示針對這個部門的主題,此時PPT就相當於函式,函式中封裝了顯示主題的功能,this相當於未來哪個部門用這套PPT的一個佔位符。
this是用於識別到底哪個環境正在呼叫我。可以理解為this是不同調用環境的一個佔位符,誰呼叫函式,未來this就是誰。
普通函式呼叫,this代表window,作為物件方法呼叫,this代表那個物件,作為建構函式呼叫,this代表新生成的那個新物件。
正如您看到的上面例子,如果沒有this,函式中如何表示不同部門的title呢?
五、apply和call難道只有JS引擎可以幫助我們確定this到底是誰嗎?我們能否手動強制指定this呢?答案是肯定的,這就需要apply和call發揮用武之地了。
apply和call的第一個引數就是手動指定的函式被呼叫的環境,也就是說,由我們寫程式碼來指定函式執行的環境,第二個引數是要傳遞給函式的引數,apply以陣列形式呈現引數,call則是非陣列形式的引數列表,還拿上面的例子改寫一下。
六、建構函式眾所周知,我們用new運算子來呼叫建構函式,那麼它經歷了以下步驟:
1、生成一個新物件;
2、將建構函式中的this繫結到新生成的物件(此時建構函式中的this代表的就是這個新物件);
3、透過new返回這個新物件。
上面的例子,並沒有什麼用途,所以從呼叫建構函式的第2步“將建構函式中的this繫結到新生成的物件”分析,這一步究竟該如何理解呢?
這一步是為了讓我們給這個新物件增加一些屬性和方法,讓它變成一個真正有用的物件,這叫做初始化。建構函式中的this一切指代這個新生成的物件,只有有意義地進行初始化後,接下來透過建構函式例項化好的物件才能真的“物有所值”,否則就失去了建構函式的意義。
七、setTimeout中的this回撥函式的意思是“回頭再來呼叫”,這裡面隱含這兩個重要的問題-何時回來呼叫以及誰來呼叫。
上面的例子我們希望1秒鐘後呼叫o.say,輸出o.name,我們儘管顯式地指定了回撥函式為o.say,但很遺憾,最後輸出的結果卻是全域性環境的name。
原因在於當用戶在瀏覽器位址列中輸入一個網址後,頁面的生命週期大致分為三個階段:
1、頁面構建階段
在這個階段,使用者向伺服器傳送請求,伺服器響應返回HTML,CSS,JS,然後瀏覽器利用返回的響應構建頁面。
構建頁面分為兩個部分,一個是解析HTML和DOM構建,另一個是一旦遇到JS程式碼,就停止解析HTML和DOM構建,轉而執行JS程式碼,JS程式碼執行中包括註冊事件處理程式,就像setTimeout中的回撥函式。當JS程式碼執行完後,繼續解析HTML和DOM構建,再次遇到JS程式碼時,依然停止解析HTML和DOM構建,執行JS程式碼...,頁面構建的兩部分是交替執行的。
2、事件處理
當頁面構建完成後,瀏覽器會根據事件列隊來處理註冊的事件處理程式。事件列隊不在頁面生命週期中,什麼時候處理事件是非同步的,例如ajax,setTimeout,滑鼠事件等,這完全是由瀏覽器控制何時處理,我們的程式碼則只是告訴瀏覽器:“你應該在ajax響應後處理我的回撥函式”,但響應是否完成只有瀏覽器最清楚了。
3、頁面銷燬
根據頁面的生命週期,可以得知是我們編寫程式碼告訴瀏覽器去處理事件,換句話說,告訴瀏覽器發起對事件處理程式的呼叫,而瀏覽器對函式的呼叫預設環境是全域性。
上例中,我們告訴瀏覽器1秒鐘後開始執行事件處理程式,setTimeout中回撥函式的this就是預設發起呼叫的全域性環境window。
如果我們想讓setTimeout的呼叫環境變為物件o,則需要強制修改函式的呼叫環境,可以使用bind方法,它和call類似,區別是bind方法並不是執行函式,而是生成(返回)一個指定好呼叫環境的的新函式。
上面的程式碼,我們告訴瀏覽器以物件o作為事件處理程式的呼叫環境。