1. 程式人生 > >《編寫可維護的JavaScript》讀書筆記之程式設計實踐-事件處理

《編寫可維護的JavaScript》讀書筆記之程式設計實踐-事件處理

事件處理

在所有 JavaScript 應用中事件處理都是非常重要的。所有的 JavaScript 均通過事件繫結到 UI 上,所以大多數前端工程師需要花費很多時間來編寫和修改事件處理程式。由於事件繫結沒有受到多大重視,大多數事件處理相關的程式碼和事件環境(對於開發者來說,每次事件觸發時才會可用)緊緊耦合在一起,導致可維護性很糟糕。

典型用法

【事件物件】:當事件觸發時,事件物件(event 物件)會作為回撥引數傳入事件處理程式中。event 物件包含所有和事件相關的資訊,包括宿主(target)以及其他和事件型別相關的資料。例如,滑鼠事件會將其位置資訊暴露在 event 物件上,鍵盤事件會將按鍵的資訊暴露在 event 物件上,觸屏事件會將觸控位置和持續時間(duration)暴露在 event 物件上。只有提供了所有這些資訊,UI 才會正確地執行互動。

【情境】:

function handleClick(event) {
    var popup = document.getElementById('popup');
    popup.style.left = event.clientX + "px";
    popup.style.top = event.clientY + "px";
    popup.className = "reveal";
}
addListener(element, "click", handleClick);

規則1:隔離應用邏輯

上段情境程式碼的第一個問題是:事件處理程式包含了應用邏輯(application logic)

。應用邏輯是和應用相關的功能性程式碼,而不是和使用者行為相關的。

【最佳實踐】:將應用邏輯從所有事件處理程式中抽離出來。

  • 或許在其他地方也會觸發同一段相同的應用邏輯:比如,有時你需要在使用者將滑鼠移到某個元素上時判斷是否顯示彈出框,或者當按下鍵盤上的某個鍵時也作同樣的邏輯判斷。
  • 方便測試:測試時需要直接觸發功能程式碼,而不必通過模擬對元素的點選來觸發。如果將應用邏輯放置於事件處理程式中,唯一的測試方法是製造事件的觸發。儘管某些測試框架可以模擬觸發事件,但實際上這不是測試的最佳方法。呼叫功能性程式碼最好的做法就是單個的函式呼叫。

【重構】:

var MyApplication = {
    handleClick: function(event) {
        this.showPopup(event);
    },
    
    showPopup: function(event) {
        var popup = document.getElementById("popup");
        popup.style.left = event.clientX + "px";
        popup.style.top = event.clientY + "px";
        popup.className = "reveal";
    }
};

addListener(element, "click", function(event) {
    MyApplication.handleClick(event);
});

【說明】:將應用邏輯剝離出去,對同一段功能程式碼的呼叫可以在多點發生,則不需要一定依賴於某個特定事件的觸發,這顯然更加方便。

規則2:不要分發事件物件

上例情境程式碼還存在一個問題:event 物件被無節制地分發。從匿名的事件處理函式傳入了 MyApplication.handleClick(),然後又傳入了 MyApplication.showPopup()。event 物件上包含很多和事件相關的額外資訊,而這段程式碼只用到了其中的兩個而已。

【原則】:應用邏輯不應當依賴於 event 物件來正確完成功能。

  • 方法介面並沒有表明哪些數是必要的。好的 API一定是對於期望和依賴都是透明的。將 event 物件作為引數並不能告訴你 event 的哪些屬性是有用的,用來幹什麼?(譯者:作者的意思是說好的 API 要更明確清楚,但這個觀點我們需要辯證地對待,如果明確知道回撥傳值的用處以及需要傳哪些值,當然更好。但更多的時候我們並不知道應用邏輯做何種事情,因此需要為應用邏輯提供儘可能多的資訊,如何利用這些資訊,效率如何統統交由應用邏輯負責,以達到某種層次的解耦。)
  • 如果你想測試這個方法,必須重新建立一個 event 物件並將它作為引數傳入。所以,需要確切地知道這個方法使用了哪些資訊,這樣才能正確地寫出測試程式碼。

【注意】:介面格式不清晰和自行構造 event 物件來用於測試在大型 Web 應用中都是不可取的。程式碼不夠清晰就會導致 bug。

【最佳實踐】:讓事件處理程式使用 event 物件來處理事件,然後拿到所有需要的資料傳給應用邏輯。

【重構】:

var MyApplication = {
    handleClick: function(event) {
        this.showwPopup(event.clientX, event.clientY);
    },
    
    showPopup: funciton(clientX, clientY) {
        var popup = document.getElementById('popup');
        popup.style.left = clientX + "px";
        popup.style.top = clientY + "px";
        popup.className = "reveal";
    }
};

addListener(element, "click", function(event) {
    MyApplication.handleClick(event);
});

// 測試應用邏輯程式碼時,不需要自行構造 event 物件。
MyApplication.showPopup(10, 10);

【注意】:當處理事件時,最好讓事件處理程式成為接觸到 event 物件的唯一的函式。事件處理程式應當在進入應用邏輯之前針對 event 物件執行任何必要的操作,包括阻止預設事件或阻止事件冒泡,都應當直接包含在事件處理程式中。

var MyApplication = {
    handleClick: function(event) {
    
        // 假設事件支援 DOM Level2
        event.preventDefault();
        event.stopPropagation();
    
        // 傳入應用邏輯
        this.showwPopup(event.clientX, event.clientY);
    },
    
    showPopup: funciton(clientX, clientY) {
        var popup = document.getElementById('popup');
        popup.style.left = clientX + "px";
        popup.style.top = clientY + "px";
        popup.className = "reveal";
    }
};

addListener(element, "click", function(event) {
    MyApplication.handleClick(event);
});