【讀書筆記】:《編寫可維護的JavaScript》第07章 事件處理

龍天下丿發表於2020-11-13

第07章 事件處理

7.1 典型用法

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

在很多場景中,你只是用到了event所提供資訊的一小部分,看下面這段程式碼。

// 不好的寫法
function handleClick(event) {
    "use strick";
    var popup = document.getElementById("popup");
    popup.style.left = event.clientX + "px";
    popup.style.top = event.clientY + "px";
    popup.className = "reveal";
}

儘管這段程式碼看起來非常簡單並且沒什麼問題,但實際上是不好的寫法,因為這種做法有侷限性。

7.2 規則1:隔離應用邏輯

上段例項程式碼的第一個問題是事件處理程式包含了應用邏輯(application logic)。應用邏輯是和應用相關的功能性程式碼,而不是和使用者行為相關的。上段程式碼中,應用邏輯是在特定位置顯示一個彈出框。儘管這個互動應當是在使用者點選某個特定元素時發生,但情況並不總是如此。

將應用邏輯從所有事件處理程式中抽離出來的做法是一種最佳實踐,因為說不定什麼時候其他地方就會觸發同一段邏輯。比如,有時你需要使用者滑鼠移到某個元素上時判斷是否顯示彈出框,或者按下鍵盤的某個鍵時也作同樣的邏輯判斷。這樣多個時間的處理程式執行了同樣的邏輯,而你的程式碼卻不小心複製了多份。

如果對上段例項程式碼進行重構,第一步是將處理彈出框邏輯程式碼放入一個單獨的函式中,這個函式很可能掛載於為該應用定義的一個全域性物件上。事件處理程式應當總是在一個相同的全域性物件中,因此就有了以下兩個方法:

var MyApplication = {
    handleClick: function (event) {
        "use strict";
        this.showPopup(event);
    },

    showPopup: function (event) {
        "use strict";
        var popup = document.getElementById("popup");
        popup.style.left = event.clientX + "px";
    }
};

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

之前的事件處理程式中包含的所有應用邏輯現在轉移到了MyApplication.showPopup()方法中。現在MyApplication.handleClick()方法只做一件事情,即呼叫MyApplication.showPopup()。

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

在剝離出應用邏輯之後,上段程式碼還存在一個問題,即event物件被無節制地分發。它從匿名的事件處理函式傳入了MyApplication.handleClick(),然後又傳入了MyApplication.showPopup()。正如上文提到的,event物件上包含很對和事件相關的額外資訊,而這段程式碼只用到了其中的兩個而已。

應用邏輯不應當依賴event物件來正確完成功能,原因如下:
如果你想測試這個方法,你必須重新建立一個event物件並將它作為引數傳入。
最佳的辦法是讓事件事件處理程式使用event物件來處理事件,然後拿到所有需要的資料傳給應用邏輯。例如 MyApplication.showPopup() 方法只需要使用兩個資料,x座標和y座標:

// 好的寫法
var MyApplication= {
    handleClick: function (event) {
        "use strict";
        this.showPopup(event.clientX, event.clientY);
    },

    showPopup: function (x, y) {
        "use strict";
        var pupup = document.getElementById("popup");
        pupup.style.left = x;
        pupup.style.top = y;
        pupup.className = "reveal";
    }
};

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

這段重寫的程式碼中,MyApplication.handleClick() 將 x 座標和 y 座標傳入了MyApplication.showPopup()。代替了之前傳入的事件物件。可以很清晰地看到MyApplication.showPopup()所期望傳入的引數,並且測試或程式碼的任意位置都可以很輕鬆地直接呼叫這段邏輯:

MyApplication. showPopup(10,20);

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

var MyApplication = {
    handleClick: function (event) {
        "use strict";
        // 假設事件支援 DOM Level2
        event.preventDefault();
        event.stopPropagation();
        this.showPopup(event.clientX, event.clientY);
    },

    showPopup: function (x, y) {
        "use strict";
        var pupup = document.getElementById("popup");
        pupup.style.left = x;
        pupup.style.top = y;
        pupup.className = "reveal";
    }
};

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

相關文章