JavaScript(4):注重關注點的分離

Blog背景圖

關注點分離(Separation of Concerns,SoC)

定義

  • 關注點分離是一種軟體設計原則,將一個系統劃分為不同的模組或元件,每個模組或元件集中於解決特定的關注點或任務,以提高系統的可操作性、可維護性、可擴展性和可重用性。該原則主張將不同層面的關注點獨立開發,使得修改一個關注點不會影響其他關注點。
  • 傳統的網頁開發中,通常會將網頁的開發區分為HTML、CSS、JavaScript三個技術領域,並分別將它們拆分成不同的檔案,以負責各自的領域。
    • HTML: 語意層,負責網頁結構和整體語意。
    • CSS: 樣式層,負責網頁外觀和樣式。
    • JavaScript: 邏輯層,負責處理事件邏輯和動態行為。
  • 避免寫行內樣式(inline style)跟行內腳本(inline script)造成彼此領域的干涉。
1
2
3
<h1 style="color:red;font-size:46px;"  onclick="alert('Hi')">
Hello World
</h1>

  • 關注點分離寫法
  • HTML(語意層):
1
2
3
<h1>
Hello World
</h1>
  • CSS(樣式層)
1
2
3
4
h1 {
color: red;
font-size: 46px;
}
  • JavaScript(邏輯層)
1
2
3
4
5
6
7
// script.js
document.addEventListener('DOMContentLoaded', function () {
const header = document.querySelector('h1');
header.addEventListener('click', function () {
alert('Hi');
});
});

關注點分離- MVC概念

  • Model:
    • 負責處理數據的邏輯,包括數據的存儲、檢索、更新和刪除等操作。
  • View:
    • 負責呈現界面,即用戶界面的渲染和顯示。
  • Controller:
    • 負責接收用戶的輸入,處理業務邏輯,然後更新模型和視圖。

  • 單純依賴Controller影響View的節點,使得View上呈現的節點難以維護。
    傳統網頁結構

我們希望透過MVC的概念讓維護上更為方便。

  • 使用者與介面互動 -> Controller處理網頁邏輯 -> Controller調用Model方法,影響Model資料(新增或修改) -> 修改完Controller再次呼叫View渲染的方法 -> View根據更新後的資料重新渲染
  • 達到View及Model透過Controller進行彼此的溝通而不直接互相影響。
    關注點分離-網頁結構
  • 透過以下流程搭配程式碼進行說明
    1. 用戶與界面互動,觸發 Controller 的相應方法。
      • 使用者在瀏覽器點擊移除。
    2. Controller 根據用戶的輸入操作 Model。
      • 按鈕的點擊事件觸發了 removeData 方法,這是 Controller 中的一個相應方法。
    3. Model 的狀態發生變化。
      • removeData方法中使用了 splice 方法來移除資料陣列 data 中的特定項目,即刪除了一個資料項。
    4. Controller 得知 Model 狀態的變化,通知相應的 View。
      • removeData方法執行完畢後,立即呼叫了 render 方法,這是通知 View 部分。
    5. View 從 Model 中獲取最新的數據。
      • render方法內部重新遍歷資料陣列,創建新的HTML元素,並更新 DOM 中的內容。
    6. View 使用這些數據更新界面。
      • 新的HTML元素被注入到DOM中,此時界面上的列表已經更新,反映了資料變化。
  • Codepen連結
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const component = {
// Model的部分
// --------
data: [
'這是第一句話',
'這是第二句話',
'這是第三句話'
],
removeData(id) {
this.data.splice(id, 1);
this.render();
},
// --------

// View的部分
// --------
renderList() {
const list = document.querySelector('.component ul');
let content = '';
this.data.forEach((item, i) => {
content = `${content}<li>${item} <button type="button" class="btn" data-id="${i}">移除</button></li>`;
});
list.innerHTML = content;
},
addEventListeners() {
const btns = document.querySelectorAll('.btn');
btns.forEach(btn => btn.addEventListener('click', (e) => {
const id = e.target.dataset.id;
this.removeData(id);
}));
},
// --------

// Controller
// --------
render() { // 渲染方法
this.renderList();
this.addEventListeners();
},
init() {
this.render();
}
// --------
};
component.init();