在JavaScript中运用DRY原则优化联动下拉框重复代码
优化联动下拉框的冗余代码,提升扩展性
我正在实现一组相互依赖的联动下拉框,构建下拉框的数据结构如下:
const data = { size: [ { id: "s1", value: "009" }, { id: "s2", value: "012" }, { id: "s3", value: "041", configId: ["c2"], dischargeId: ["d3"] }, ], config: [ { id: "c1", value: "Horizontal", sizeId: ["s1", "s2"], dischargeId: ["d1", "d2"] }, { id: "c2", value: "Vertical", dischargeId: ["d3"] }, ], discharge: [ {id: "d1", value: "S-Straight", sizeId: ["s1", "s2"], configId: ["c1"] }, {id: "d2", value: "E-End", sizeId: ["s1", "s2"], configId: ["c1"] }, {id: "d3", value: "T-Top", configId: ["c2"] }, ] }
部分数据对象带有额外属性,比如size中id为s3的对象包含configId和dischargeId属性,用来控制联动逻辑:
- 当配置下拉框选中id为
c1的选项时,size下拉框的s3选项需要禁用 - 当排放下拉框选中
d1或d2时,s3也需禁用
其他属性遵循类似的联动逻辑。
目前代码功能正常,但存在大量重复逻辑,而且未来可能新增更多下拉项类型,希望优化代码,让它简洁低冗余,易于扩展。
当前实现代码如下:
const data = { size: [ { id: "s1", value: "009" }, { id: "s2", value: "012" }, { id: "s3", value: "041", configId: ["c2"], dischargeId: ["d3"] }, ], config: [ { id: "c1", value: "Horizontal", sizeId: ["s1", "s2"], dischargeId: ["d1", "d2"] }, { id: "c2", value: "Vertical", dischargeId: ["d3"] }, ], discharge: [ {id: "d1", value: "S-Straight", sizeId: ["s1", "s2"], configId: ["c1"] }, {id: "d2", value: "E-End", sizeId: ["s1", "s2"], configId: ["c1"] }, {id: "d3", value: "T-Top", configId: ["c2"] }, ] } const sizeSelect = document.getElementById('size') const configSelect = document.getElementById('configuration') const dischargeSelect = document.getElementById('discharge') const createSelect = (values, select) => { values.forEach(t => { let opt = document.createElement('option') opt.value = t.id opt.text = t.value select.append(opt) }) } createSelect(data.size, sizeSelect) createSelect(data.config, configSelect) createSelect(data.discharge, dischargeSelect) sizeSelect.addEventListener('change', (e) => { const val = e.target.value const size = data.size.find(t => t.id == val) if(size.configId){ Array.from(configSelect.querySelectorAll('option')).forEach(o => o.disabled = true) size.configId.forEach(sId => configSelect.querySelector(`option[value="${sId}"]`).disabled = false) } if(size.dischargeId){ Array.from(dischargeSelect.querySelectorAll('option')).forEach(o => o.disabled = true) size.dischargeId.forEach(sId => dischargeSelect.querySelector(`option[value="${sId}"]`).disabled = false) } }); configSelect.addEventListener('change', (e) => { const val = e.target.value const config = data.config.find(s => s.id == val) if(config.sizeId){ Array.from(configSelect.querySelectorAll('option')).forEach(o => o.disabled = false) Array.from(sizeSelect.querySelectorAll('option')).forEach(o => o.disabled = true); config.sizeId.forEach(sId => sizeSelect.querySelector(`option[value="${sId}"]`).disabled = false) } if(config.dischargeId){ Array.from(dischargeSelect.querySelectorAll('option')).forEach(o => o.disabled = true) config.dischargeId.forEach(sId => dischargeSelect.querySelector(`option[value="${sId}"]`).disabled = false) } }); dischargeSelect.addEventListener('change', (e) => { const val = e.target.value const discharge = data.discharge.find(s => s.id == val) if(discharge.sizeId){ Array.from(sizeSelect.querySelectorAll('option')).forEach(o => o.disabled = true); discharge.sizeId.forEach(sId => sizeSelect.querySelector(`option[value="${sId}"]`).disabled = false); } if(discharge.configId) { Array.from(configSelect.querySelectorAll('option')).forEach(o => o.disabled = true); discharge.configId.forEach(sId => configSelect.querySelector(`option[value="${sId}"]`).disabled = false); } });
<select id="size"></select> <select id="configuration"></select> <select id="discharge"></select>
优化方案
优化核心思路
- 统一DOM与数据映射:用对象管理所有下拉框元素和对应的数据集合,新增下拉项只需补充映射关系
- 提取通用函数:将创建选项、更新禁用状态、处理变更事件的逻辑封装成通用函数,消除重复代码
- 自动化联动规则:通过属性名后缀
Id自动识别联动目标,无需为每个联动关系编写单独判断
优化后的完整代码
<select id="size"></select> <select id="configuration"></select> <select id="discharge"></select> <script> const data = { size: [ { id: "s1", value: "009" }, { id: "s2", value: "012" }, { id: "s3", value: "041", configId: ["c2"], dischargeId: ["d3"] }, ], config: [ { id: "c1", value: "Horizontal", sizeId: ["s1", "s2"], dischargeId: ["d1", "d2"] }, { id: "c2", value: "Vertical", dischargeId: ["d3"] }, ], discharge: [ {id: "d1", value: "S-Straight", sizeId: ["s1", "s2"], configId: ["c1"] }, {id: "d2", value: "E-End", sizeId: ["s1", "s2"], configId: ["c1"] }, {id: "d3", value: "T-Top", configId: ["c2"] }, ] }; // 下拉框key与DOM元素的映射,新增下拉项只需在此添加 const selectMap = { size: document.getElementById('size'), config: document.getElementById('configuration'), discharge: document.getElementById('discharge') }; // 通用创建下拉框选项函数 function createOptions(dataKey) { const select = selectMap[dataKey]; data[dataKey].forEach(item => { const opt = document.createElement('option'); opt.value = item.id; opt.text = item.value; select.appendChild(opt); }); } // 通用更新下拉框禁用状态函数 function updateSelectEnabled(targetKey, allowedIds) { const targetSelect = selectMap[targetKey]; // 先禁用所有选项 Array.from(targetSelect.options).forEach(opt => opt.disabled = true); // 启用允许的选项 allowedIds.forEach(id => { const opt = targetSelect.querySelector(`option[value="${id}"]`); if (opt) opt.disabled = false; }); } // 通用下拉框变更事件处理函数 function handleSelectChange(dataKey) { return function(e) { const selectedId = e.target.value; const selectedItem = data[dataKey].find(item => item.id === selectedId); if (!selectedItem) return; // 遍历当前选中项的所有属性,自动识别联动规则 Object.keys(selectedItem).forEach(key => { if (key.endsWith('Id')) { const targetKey = key.slice(0, -2); // 从configId提取config if (selectMap[targetKey]) { updateSelectEnabled(targetKey, selectedItem[key]); } } }); }; } // 初始化所有下拉框 Object.keys(selectMap).forEach(key => { createOptions(key); selectMap[key].addEventListener('change', handleSelectChange(key)); }); </script>
优化效果说明
- 高扩展性:新增下拉项时,只需在
data中添加对应数据集合,在selectMap中补充DOM元素映射,核心逻辑无需修改 - 低冗余:所有重复的DOM操作、联动逻辑都被封装成通用函数,代码量大幅减少
- 易维护:联动规则通过属性名自动识别,逻辑清晰,后续修改或新增联动关系更便捷
内容的提问来源于stack exchange,提问作者Hello World
相关产品推荐
相关产品推荐

