如何重构Etch-A-Sketch JS代码实现DRY?彩虹模式异常排查
解决Etch-A-Sketch的代码重复与Rainbow模式颜色问题
让我们一步步解决你的问题,先理清核心问题所在,再重构代码遵循DRY原则并修复颜色bug:
问题根源分析
- Rainbow模式颜色不随机:你当前调用
pickColor('rgb(' + randColor() + ')')时,randColor()只会执行一次,生成一个固定颜色传给pickColor,所有网格的mouseover事件都会复用这个颜色。我们需要每个mouseover触发时实时生成新颜色。 - 代码重复:
eraseGrid()和drawRainbow()的逻辑几乎完全一致,只是操作的按钮和颜色规则不同,完全可以抽象成公共函数。
重构后的完整代码
/**************************** Input->Button DOM ****************************/ const newGrid = document.getElementById('new-grid'); newGrid.addEventListener('click', createGrid); const erase = document.getElementById('erase'); erase.addEventListener('click', () => toggleMode(erase, 'erase', rainbow, 'rainbow', '#f2f2f2')); const rainbow = document.getElementById('rainbow'); rainbow.addEventListener('click', () => toggleMode(rainbow, 'rainbow', erase, 'erase', getRandomColor)); /*********************** Grid variable and creation ***********************/ const main = document.querySelector('main'); // 初始化16x16网格 drawGrid(16, ((600 / 16) - 2) + 'px'); setGridColor('#333'); // 创建新网格 function createGrid() { // 移除现有网格元素(从后往前删避免索引混乱) for (let i = main.childNodes.length - 1; i >= 0 ; i--) { main.removeChild(main.childNodes[i]); } let size; do { size = parseInt(prompt("Please enter a number from 1 to 64", ""), 10); } while(Number.isNaN(size) || size > 64 || size < 1); const numPx = (600 / size) - 2; const px = numPx + 'px'; drawGrid(size, px); setGridColor('#333'); } // 设置网格颜色:支持固定颜色值或返回颜色的函数 function setGridColor(colorOrFunc) { // 先移除所有旧的mouseover事件(避免重复绑定) Array.from(main.children).forEach(cell => { if (cell.classList.contains('clear')) return; // 跳过清除浮动的div cell.removeEventListener('mouseover', handleCellHover); cell.addEventListener('mouseover', handleCellHover); }); // 处理hover的逻辑函数 function handleCellHover() { const color = typeof colorOrFunc === 'function' ? colorOrFunc() : colorOrFunc; this.style.backgroundColor = color; } } // 绘制网格 function drawGrid(size, px) { for (let i = 0; i < size; i++) { for (let j = 0; j < size; j++) { const div = document.createElement('div'); main.appendChild(div); div.setAttribute('style', `width: ${px}; height: ${px}; float: left; border: 1px solid #333;`); } } // 清除浮动的div const clearDiv = document.createElement('div'); clearDiv.setAttribute('class', 'clear'); main.appendChild(clearDiv); } // 抽象模式切换逻辑:处理按钮激活状态+设置网格颜色规则 function toggleMode(activeBtn, activeClass, inactiveBtn, inactiveClass, colorRule) { activeBtn.classList.toggle(activeClass); inactiveBtn.classList.remove(inactiveClass); if (activeBtn.classList.contains(activeClass)) { setGridColor(colorRule); } else { // 切换回默认黑色 setGridColor('#333'); } } // 生成随机RGB颜色字符串 function getRandomColor() { const r = Math.floor(Math.random() * 256); const g = Math.floor(Math.random() * 256); const b = Math.floor(Math.random() * 256); return `rgb(${r}, ${g}, ${b})`; }
关键改进点说明
修复Rainbow颜色随机问题:
- 把
pickColor改名为setGridColor,它现在可以接受固定颜色值或者返回颜色的函数。 - Rainbow模式传入
getRandomColor函数,每次mouseover触发时,都会调用这个函数生成新的随机颜色,而不是用固定值。 - 新增
handleCellHover内部函数,负责在hover时动态获取颜色。
- 把
消除代码重复(遵循DRY原则):
- 抽象出
toggleMode函数,统一处理按钮的激活状态切换(添加/移除class)、取消其他按钮的激活状态,以及设置对应的颜色规则。 erase和rainbow按钮的点击事件现在只需要调用toggleMode,传入对应的参数即可,无需重复写逻辑。
- 抽象出
额外优化:
- 在
setGridColor中先移除旧的mouseover事件,避免重复绑定导致多次触发颜色变化。 - 简化
getRandomColor函数,直接返回RGB字符串,无需数组拼接。 - 给清除浮动的div命名更清晰(
clearDiv),避免变量名冲突。
- 在
这样修改后,不仅解决了代码重复问题,Rainbow模式也能实现每个网格mouseover时生成不同的随机颜色了。
内容的提问来源于stack exchange,提问作者user7217977
相关产品推荐
相关产品推荐

