原文鏈接:http://www.tuicool.com/articles/z2Yvaq
任何變量或對象都有其賴以生存的上下文。如果簡單地將對象理解為一段代碼,那么對象處在不同的上下文,這段代碼也會執行出不同的結果。
例如,我們定義一個函數 getUrl 和一個對象 pseudoWindow 。
function getUrl() { console.log(this.document.URL); } var pseudoWindow = { document: { URL: "I'm fake URL" }, getUrl1: getUrl, getUrl2: function (callback) { callback(); this.func = callback; this.func(); } }
執行 getUrl() ,打印出當前頁面的 URL。
執行 pseudoWindow.getUrl1() ,打印出 I'm fake URL 。
執行 pseudoWindow.getUrl2(getUrl) ,先打印出當前頁面 URL,后打印 I'm fake URL 。
下面讓我們用最簡單粗暴的語言來解釋以上代碼。
概念
什么是 this?
this 就是函數調用使用的上下文。
什么是上下文?
上下文是在句號標記法中,句號前面的那個東西。
例如 pseudoWindow.getUrl1 , pseudoWindow 是 pseudoWindow.getUrl1() 的上下文。
什么是自由變量?
當一個變量沒有綁定到任何上下文時(或者說綁定到頂級作用域時,例如瀏覽器中的 window),它就是 自由變量 。
什么是變量與對象?
變量就是代碼中你所用的標識符,一個標識符就是一個變量,多個變量可能指向同一個對象。例如:
pseudoWindow.getUrl1 === getUrl // 得到 true
變量所處的上下文就是對象的作用域。
代碼分解
調用 getUrl()
首先 getUrl 函數是定義在全局環境中,它是一個自由變量,在瀏覽器中(以下描述均為瀏覽器環境)它的上下文就是 window ,所以 window.getUrl() 和 getUrl()是等價的。因此 this 指向 window 對象,打印出當前 URL。
調用 pseudoWindow.getUrl1()
首先 pseudoWindow 是一個對象,它可以充當上下文角色。我們給它定義了一個屬性 getUrl1 ,你可以將屬性視為被綁定到某個上下文的變量,變量 getUrl1 本身又指向了變量 getUrl 所指向的對象,所以 pseudoWindow.getUrl1 === getUrl 才會為 true 。
當我們調用 pseudoWindow.getUrl1() 時,它的意思是執行 getUrl() 這段代碼,執行代碼所需的參數為空,上下文為 pseudoWindow 。
所以函數中的 this 指向了 pseudoWindow ,而 pseudoWindow 對象恰好又有 document 屬性,該屬性恰好又有 URL 屬性,因此打印出 I'm fake URL 。
調用 pseudoWindow.getUrl2(getUrl)
同理我們又定義了一個變量 getUrl2 ,並綁定到 pseudoWindow 對象身上,使之成為后者的一個屬性。而這個屬性本身又指向一個匿名函數,我們姑且稱之為 A,該函數對象接受另一個函數對象作為回調函數。
因此執行 pseudoWindow.getUrl2(getUrl) 時,意思是執行代碼 A,執行代碼所需的參數為 getUrl 這段代碼,上下文為 pseudoWindow 。
因此函數 A 中的 this 指向了 pseudoWindow 。
當程序執行到函數 A 內部的 callback() 時,因為變量 callback 沒有綁定到任何上下文,因此它相當於一個自由變量,它的上下文就指向了 window 對象,因此首先打印出當前頁面的 URL。
接下來 this.func = callback 意味着三件事:
- 我們新申明了一個變量
func。 - 通過
=操作符,我們將該變量指向了callback所指向的函數對象。 - 通過
.操作符,我們將該變量綁定到了this對象上,使之成為后者的一個屬性,而本例中this指向的就是pseudoWindow對象。
於是當程序執行到 this.func() 時,它的意思是執行 callback 這段代碼,執行代碼所需的參數為空,上下文為 pseudoWindow 。於是打印出了 I'm fake URL 。
這段代碼帶來的一個副作用是我們隱式地為 pseudoWindow 對象添加了一個新的屬性 func ,如果我們想要通過回調的方式打印出 pseudoWindow 的 document.URL 屬性,又不想對 pseudoWindow 對象造成任何影響,那么我們可以使用函數的 apply 方法。所有函數都有 apply 方法,它會將它接收的第一個參數設置為函數的上下文。
例如本例中我們可以改寫代碼成這樣子:
var pseudoWindow = { document: { URL: "I'm fake URL" }, getUrl1: getUrl, getUrl2: function (callback) { callback(); callback.apply(this); } }
嚴格地說,你應該先檢查 callback 參數類型是否是函數對象。
總結
Javascript 支持將函數作為參數傳遞,回調函數變量指向的函數對象都未與任何上下文綁定,所有未與明確上下文綁定的變量都是自由變量,瀏覽器器中所有自由變量的上下文都是 window 對象。
