页面里藏了两个「传统 script」和三个「模块 script」,按钮负责把它们的老底翻出来。
这就是第十四章迷你待办的拆分版。沙箱 iframe 里加载不了外部模块文件(模块要走 CORS),所以下面第三节跑的是这三个文件的内联等价版——真实项目里拆成文件后,代码一字不差。
// utils.js —— 纯工具:不碰 DOM,不做业务 export function clamp(n, min, max) { return Math.min(Math.max(n, min), max) } function pad2(n) { // 不 export:只服务本文件,外面看不见 return String(n).padStart(2, '0') } export function formatTime(date) { const h = pad2(date.getHours()) const m = pad2(date.getMinutes()) const s = pad2(date.getSeconds()) return h + ':' + m + ':' + s }
// store.js —— 数据与规则:管状态,不碰 DOM import { clamp } from './utils.js' const state = { count: 0, limit: 10 } // 模块内部状态,外部拿不到引用 export function increment() { state.count = clamp(state.count + 1, 0, state.limit) return state.count } export function reset() { state.count = 0 return state.count } export function getState() { return { ...state } // 给外面一份拷贝,防乱改 }
// main.js —— 入口:只做接线员,把数据和 DOM 连起来
import { increment, reset, getState } from './store.js'
import { formatTime } from './utils.js'
const countEl = document.querySelector('#count')
const noteEl = document.querySelector('#m-note')
function render() {
countEl.textContent = getState().count
}
document.querySelector('#btn-plus').addEventListener('click', () => {
const n = increment()
noteEl.textContent = '[' + formatTime(new Date()) + '] count → ' + n
render()
})
document.querySelector('#btn-reset').addEventListener('click', () => {
reset()
noteEl.textContent = '已重置'
render()
})
render()