因此,您在示例中描述的並不是代理模式的演示,而是關於「調用對象」以及它如何在JavaScript中工作的混淆示例。
在JavaScript中,函數是「一流的」。這基本上意味着函數就像任何其他數據一樣是數據。因此,讓我們考慮以下情況:
var fn = (function() { return this.x; }),
a = {
x : 1,
fn : fn,
},
x = 2,
nothing = (function (z) { return z; });
所以,我們有一個對象a
,它有兩個屬性:fn
和x
。我們也有變量x
,fn
(這是一個返回this.x
的函數)和nothing
(它返回任何它傳遞的)。
如果我們評估a.x
,我們得到1
。如果我們評估x
,我們得到2
。很簡單,呃?現在,如果我們評估nothing(a.x)
,那麼我們得到1
。這也很簡單。但重要的是要認識到與屬性a.x
相關聯的值1
不以任何方式連接到對象a
。它獨立存在,可以簡單地作爲一個值傳遞。
在JavaScript中,函數的工作方式相同。屬性函數(通常稱爲「方法」)可以作爲簡單引用傳遞。但是,如果這樣做,他們可能會脫離對象。當您在函數中使用關鍵字this
時,這變得很重要。
this
關鍵字引用「調用對象」。當函數被評估爲時,這是與函數相關聯的對象。有設置一個函數調用對象的三個基本途徑:
- 如果函數稱爲使用點運算符(如
a.fn()
),所涉及的對象(在本例中,a
)被設置爲調用對象。
- 如果使用函數的
call
或apply
屬性調用函數,那麼您可以明確地設置調用對象(我們將看到爲什麼這在一秒鐘內有用)。
- 如果沒有通過方法1或方法2設置調用對象,則使用全局對象(在瀏覽器中,這通常稱爲
window
)。
所以,回到我們的代碼。如果我們撥打a.fn()
,它會評估爲1
。這是因爲使用點運算符,函數中的this
關鍵字將被設置爲a
。但是,如果我們簡單地調用fn()
,它將返回2
,因爲它引用了全局對象的x
屬性(表示我們使用全局x
)。
現在,這裏的事情變得棘手。如果你打電話給:nothing(a.fn)()
?您可能會驚訝於結果爲2
。這是因爲通過a.fn
到nothing()
傳遞給fn
的引用,但不保留調用對象!
這與您的編碼示例中發生的情況是相同的概念。如果您的功能handleDrop
要使用關鍵字this
,您會發現它具有不同的值,具體取決於您使用的處理程序表單。這是因爲在第二個示例中,您傳遞的是對handleDrop
的引用,但與我們的nothing(a.fn)()
示例一樣,在調用該對象時,調用對象引用將丟失。
因此,讓我們添加別的東西拼圖:
var b = {
x : 3
};
你會注意到,雖然b
有x
屬性(從而滿足fn
的使用this
的要求),它不有財產參考fn
。因此,如果我們想撥打fn
函數並將其this
設置爲b
,那麼看起來我們需要將一個新屬性添加到b
。但是相反,我們可以使用fn
上述apply
方法明確設置b
作爲呼叫對象:
fn.apply(b); //is 3
這可以用來「永久」通過創建一個新的功能「包裝綁定調用對象的功能。 「它不是永久綁定的,它只是創建一個新函數,用所需的調用對象調用舊函數。這樣的工具通常是這樣寫這樣:
Function.prototype.bind = function (obj) {
var self = this;
return function() {
return self.apply(obj, arguments);
};
};
所以執行該代碼後,我們可以做到以下幾點:
nothing(a.fn.bind(a))(); //is 1.
沒什麼棘手。實際上,bind()
屬性內置於ES5中,其工作方式與上面的簡單代碼非常相似。我們的bind
代碼實際上是一種非常複雜的方式,可以讓我們做更簡單的事情。由於a
具有fn
作爲屬性,因此我們可以使用點運算符直接調用它。我們可以跳過所有令人困惑的使用call
和apply
。我們只需要確定函數何時被調用,它就會使用點運算符來調用。我們可以看到如何做到這一點上面,但在實踐中,它遠遠更簡單,更直觀:
nothing(function() { return a.fn(); })(); //is 1
一旦你的數據引用如何可以存儲在關閉範圍的理解,如何函數是第一類對象,以及調用對象如何工作,這一切都變得非常簡單,理解和合理直觀。
至於「代理」,那些也利用相同的概念掛鉤功能。因此,假設您想要統計a.fn
被調用的次數。你可以通過插入一個代理,像這樣(從上面利用的一些概念從我們bind
代碼):
var numCalls = (function() {
var calls = 0, target = a.fn;
a.fn = (function() {
calls++;
return target.apply(a, arguments);
});
return (function() {
return calls;
});
}());
所以,現在,只要您撥打numCalls()
,它將返回的時間a.fn()
數被稱爲而沒有實際修改a.fn
的功能!這很酷。但是,您必須記住,您確實更改了a.fn
所引用的功能,因此回到代碼的開頭,您會注意到a.fn
不再與fn
相同,因此不能再互換使用。但現在的原因應該非常明顯!
我知道這基本上是一段JavaScript教育,只有幾頁的文字,但這只是簡單而已。一旦理解了這些概念,許多JavaScript模式的功能,實用性和強大功能就變得非常容易理解。
希望讓事情更清晰!
更新:感謝@ pimvdb指出我不必要的使用[].slice.call(arguments, 0)
。我已經刪除了它,因爲它是沒有必要的。
試着'console.log(這個)'而不是你的警報,它應該變得更清楚。 – bfavaretto