1

我學習JavaScript代理模式,但我仍然沒有得到,我可以從中受益。因此,我想向你提供兩個例子,並請你指出它們之間的區別。JavaScript代理模式的解釋

請看看下面的代碼:

  • 兩者有什麼addEventListener調用之間的區別?其中一人定期致電handleDrop。另一個使用代理模式。
  • 使用代理模式方法我能獲得什麼?

我測試了這兩個函數,並且都成功調用handleDrop

DndUpload.prototype.buildDropZone = function() 
{ 
    var self = this, 

    this.dropZone.addEventListener('drop', function (e) { self.handleDrop.call(self, e) }, false); 
    this.dropZone.addEventListener('drop', self.handleDrop, false); 


    DndUpload.prototype.handleDrop = function (e) 
    { 
     alert("test"); 
     ... 
    }; 
} 

您可以給我提供很好的參考,其中包含JavaScript代理模式的非常清晰的說明。

在此先感謝。

+0

試着'console.log(這個)'而不是你的警報,它應該變得更清楚。 – bfavaretto

回答

8

因此,您在示例中描述的並不是代理模式的演示,而是關於「調用對象」以及它如何在JavaScript中工作的混淆示例。

在JavaScript中,函數是「一流的」。這基本上意味着函數就像任何其他數據一樣是數據。因此,讓我們考慮以下情況:

var fn = (function() { return this.x; }), 
    a = { 
     x : 1, 
     fn : fn, 
    }, 
    x = 2, 
    nothing = (function (z) { return z; }); 

所以,我們有一個對象a,它有兩個屬性:fnx。我們也有變量x,fn(這是一個返回this.x的函數)和nothing(它返回任何它傳遞的)。

如果我們評估a.x,我們得到1。如果我們評估x,我們得到2。很簡單,呃?現在,如果我們評估nothing(a.x),那麼我們得到1。這也很簡單。但重要的是要認識到與屬性a.x相關聯的值1不以任何方式連接到對象a。它獨立存在,可以簡單地作爲一個值傳遞。

在JavaScript中,函數的工作方式相同。屬性函數(通常稱爲「方法」)可以作爲簡單引用傳遞。但是,如果這樣做,他們可能會脫離對象。當您在函數中使用關鍵字this時,這變得很重要。

this關鍵字引用「調用對象」。當函數被評估爲時,這是與函數相關聯的對象。有設置一個函數調用對象的三個基本途徑:

  1. 如果函數稱爲使用點運算符(如a.fn()),所涉及的對象(在本例中,a)被設置爲調用對象。
  2. 如果使用函數的callapply屬性調用函數,那麼您可以明確地設置調用對象(我們將看到爲什麼這在一秒鐘內有用)。
  3. 如果沒有通過方法1或方法2設置調用對象,則使用全局對象(在瀏覽器中,這通常稱爲window)。

所以,回到我們的代碼。如果我們撥打a.fn(),它會評估爲1。這是因爲使用點運算符,函數中的this關鍵字將被設置爲a。但是,如果我們簡單地調用fn(),它將返回2,因爲它引用了全局對象的x屬性(表示我們使用全局x)。

現在,這裏的事情變得棘手。如果你打電話給:nothing(a.fn)()?您可能會驚訝於結果爲2。這是因爲通過a.fnnothing()傳遞給fn的引用,但不保留調用對象!

這與您的編碼示例中發生的情況是相同的概念。如果您的功能handleDrop要使用關鍵字this,您會發現它具有不同的值,具體取決於您使用的處理程序表單。這是因爲在第二個示例中,您傳遞的是對handleDrop的引用,但與我們的nothing(a.fn)()示例一樣,在調用該對象時,調用對象引用將丟失。

因此,讓我們添加別的東西拼圖:

var b = { 
    x : 3 
}; 

你會注意到,雖然bx屬性(從而滿足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作爲屬性,因此我們可以使用點運算符直接調用它。我們可以跳過所有令人困惑的使用callapply。我們只需要確定函數何時被調用,它就會使用點運算符來調用。我們可以看到如何做到這一點上面,但在實踐中,它遠遠更簡單,更直觀:

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)。我已經刪除了它,因爲它是沒有必要的。

+0

+1,你好皮特..你的回答令人震驚。感謝您撰寫這麼多信息。我不得不重新閱讀它幾次,我需要獲得一些練習。不過,你用最好的方式解釋了它。這些例子很棒。我仍想保持這個線索幾天,看看,如果有人把這裏的一些其他信息。但是您的帖子是「已接受答案」狀態的候選人#1。謝謝。 –

+0

這是一個非常體面的解釋。我只是想補充一點,'[] .slice.call'沒有必要 - 你可以將'arguments'傳遞給'.apply'。 – pimvdb

+1

@pimvdb我不確定,我在做大部分工作時沒有真正測試我的代碼(我知道,那是調皮的)。所以我玩的很安全。你永遠不知道在任何特定的情況下「Array-ish」參數如何。我會更新修復(一旦我確認你是正確的)。 :-) – Pete

3

基本上,通過self.handleDrop直接在功能上等同於通過下面的函數:

function() { 
    return self.handleDrop.apply(this, arguments); 
} 

因爲一切都通過傳遞到原來的功能:

  • this
  • 的參數
  • 返回值

考慮到這一點,比較你的功能如下:

function(e) { self.handleDrop.call(self, e) } 
function() { return self.handleDrop.apply(this, arguments); } 

您的代理方式的區別是:

  • 它不通過傳遞返回值。
  • 它不穿過所有參數(僅第一,e
  • 它沒有通過this值通過,但使用預定義的一個:self

現在,前兩項在這裏沒有區別,因爲addEventListener不關心返回值,它也只傳遞一個參數。

但是第三項很重要:它在函數中設置了不同的this值。默認情況下,this是您將事件綁定到的元素(它由瀏覽器設置)。使用代理方式,您可以設置另一個this值。

現在,在您的代碼片段中,並不完全清楚爲什麼每次調用buildDropZone時都要設置原型函數。通常你只定義原型函數一次。但是當您使用代理方式調用處理程序handleDrop時,this指的是DndUpload實例,它與一般的原型函數一致。

+0

+1,你好,非常感謝你的解釋。它確實非常好。總結ti,代理模式爲我提供了一個控制輸入到該函數的變量的層次。在向函數輸入變量之前,函數的執行可能會有點延期,並且變量可以通過代理函數進行預處理。我理解正確嗎? –

+0

@ Bunkai.Satori:是的,這是一個很好的描述。代理函數接收事物('this',arguments)並且可以在調用原始函數之前/之後修改它們。 – pimvdb

2

考慮下面的代碼:

function printThis() { 
    console.log(this); 
} 

var someObject = { 
    performTest : function() { 
    var self = this; 

    someOtherObject.higherOrderFunction(printThis); 
    someOtherObject.higherOrderFunction(function(){printThis.call(self)}); 
    } 
} 

var someOtherObject = { 
    higherOrderFunction : function(f) { 
     f(); 
    } 
} 

什麼會someOtherObject.higherOrderFunction(printThis)回報? someOtherObject.higherOrderFunction(function(){printThis.call(self)})

第一個問題的答案取決於你是誰以及如何調用someObject.performTest()。如果我從全局上下文中調用someObject.performTest(),它可能會打印Window

無論如何,第二個將始終打印someObject實例。

當您想精確控制函數的執行上下文時,您稱之爲閉包或'代理模式'就派上用場了。

注意:在JavaScript中的this不像其他語言(例如Java)中的行爲。

+0

+1,您好,感謝您的回答。如果我理解正確,這不同,當稱爲標準調用'someOtherObject.higherOrderFunction(printThis);'與代理函數'someOtherObject.higherOrderFunction(function(){printThis.call(self)});'。然而,它在我看來很合乎邏輯,因爲代理函數function(){printThis.call(self)'包含在父函數中。但是,它從哪裏獲得實際利益?爲什麼我希望使用代理模式? –

+0

當你想確保你剛剛傳遞給higherOrderFunction的函數以self作爲'this'執行時,它是有益的。否則,'this'將根據higherOrderFunction的調用方式而有所不同。 – Hyangelo

+0

啊,所以如果我希望保持代碼的一致性,另一個好處是。有時候,'this'可能會有所不同,特別是,如果函數是一個CALLBACK。然而,如果我希望確保函數將被稱爲僅有一個特定對象的成員,那麼使用代理模式是很好的理由。 –