点击新增年度按钮时select下拉字段重复渲染翻倍问题
问题原因
两个核心逻辑错误导致下拉框重复叠加:
- 用
targetDiv.innerHTML +=拼接内容的写法会重绘formCON容器内所有已存在的DOM,之前TomSelect渲染生成的自定义下拉结构会被销毁,还原成原生select标签 initSelect方法里的重复初始化判断无效:TomSelect初始化后不会给原生select加dropdown类,导致每次调用方法都会遍历页面所有.js-select元素,给旧的select重复绑定TomSelect实例,点击次数越多重复绑定的实例越多,下拉框就会成倍叠加- 额外规范问题:动态生成的DOM存在大量重复id(比如多个
id="locationLabel"、id="Cash"),子容器和父容器都用了id="formCON",违反HTML id唯一的规范,会引发其他选择异常。
修复步骤
- 替换DOM拼接方式:不用innerHTML全量覆盖,改用
insertAdjacentHTML在容器末尾追加新表单块,不破坏已有已初始化的DOM结构 - 修正初始化逻辑:给已经完成TomSelect初始化的select加专属标记类,初始化时直接跳过带标记的元素;更稳妥的方式是追加完新表单后,只查找新表单块内部的select做初始化,不遍历全页面旧元素
- 修正重复id问题:所有动态生成的元素id拼接递增索引值,保证全局id唯一
修复后完整代码
<!-- HTML部分 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script> <input id="noOfYears" type="number" value="1" /> <div id="formCON"></div>
let i = 1; function GetYear() { var targetDiv = document.getElementById("formCON"); // 新表单块用唯一类名,不要和父容器重id,所有动态id拼接i保证唯一 const newFormHtml = `<div class="cantainer form-item" data-index="${i}"> <div class="tom-select-custom mb-3 w-100 mt-4"> <div class="d-flex justify-content-between align-items-center flex-wrap"> <label>Year</label> <select class="js-select form-select locationlabel" name="year_${i}" id="locationLabel_${i}"> <option selected disabled>select</option> <option>2022</option> <option>2021</option> <option>2020</option> <option>2019</option> <option>2018</option> <option>2017</option> <option>2016</option> <option>2015</option> <option>2014</option> </select> </div> </div> <div class="d-flex justify-content-between maskNumber mb-4 align-items-center flex-wrap"> <label>Total Community <br> investment($)</label> <input type="text" class="form-control locationlabel" name="totalinvestement_${i}" id="investement_${i}" placeholder="please enter"> </div> <div class="d-flex justify-content-between align-items-center mb-4 flex-wrap"> <label for="Addresslabel">Pre-tax-profit ($)</label> <input type="text" class="form-control locationlabel" name="Pre-tax-profit_${i}" id="Pre-tax-profit_${i}" placeholder="please enter"> </div> <div class="d-flex justify-content-between align-items-center mb-4 flex-wrap"> <label for="Addresslabel">Earnings before<br> interest,<br> tax, depreciation<br> & amortization<br>(EBITDA) ($)</label> <input type="text" class="form-control locationlabel" name="depreciation_${i}" id="depreciation_${i}" placeholder="please enter"> </div> <div class="tom-select-custom mb-3 w-100 mt-4"> <div class="d-flex justify-content-between align-items-center flex-wrap"> <label>Balance Date</label> <select class="js-select form-select locationlabel" name="date_${i}" id="dateLabel_${i}"> <option active disabled selected>Select</option> <option>June 30</option> <option>September 30</option> <option>December 31</option> <option>March 31</option> <option>Other</option> </select> </div> </div> <h4 class="card-header-title mb-6 mt-6">Itemisation</h4> <div class="d-flex justify-content-between align-items-center w-100 mb-4 flex-wrap"> <label for="Addresslabel">Cash ($)</label> <input type="text" class=" form-control locationlabel"name="Cash_${i}" id="Cash_${i}" placeholder="please enter"> </div> <div class="d-flex justify-content-between align-items-center w-100 mb-4 flex-wrap"> <label for="Addresslabel">Volunteering ($)</label> <input type="text" class=" form-control locationlabel" name="volunteercash_${i}" id="volunteerCash_${i}" placeholder="please enter"> </div> <div class="d-flex justify-content-between align-items-center w-100 mb-4 flex-wrap"> <label for="Addresslabel">Products ($)</label> <input type="text" class="form-control locationlabel" name="productcash_${i}" id="productCash_${i}" placeholder="please enter"> </div> <div class="d-flex justify-content-between align-items-center w-100 mb-4 flex-wrap"> <label for="Addresslabel">Partnerships ($)</label> <input type="text" class="form-control locationlabel" name="partnercash_${i}" id="partnerCash_${i}" placeholder="please enter"> </div> <div class="d-flex justify-content-between align-items-center w-100 mb-4 flex-wrap"> <label for="Addresslabel">Management <br> Cost ($)</label> <input type="text" class=" form-control locationlabel" name="managecash_${i}" id="manageCash_${i}" placeholder="please enter"> </div> <div class="d-flex justify-content-between align-items-center w-100 mb-4 flex-wrap"> <label for="Addresslabel">Foregone <br> Revenue($)</label> <input type="text" class="form-control locationlabel" name="revenue_${i}" id="revenue_${i}" placeholder="please enter"> </div> <div class="d-flex justify-content-between align-items-center w-100 mb-4 flex-wrap"> <label for="Addresslabel">Leverage ($)</label> <input type="text" class="form-control locationlabel" name="leveragecash_${i}" id="leverageCash_${i}" placeholder="please enter"> </div> <a href="javascript:;" class="js-create-field form-link" onclick="GetYear()"> <i class="bi-plus-circle me-1"></i> Add another Year </a> </div>` // 用beforeend追加,不重绘已有DOM targetDiv.insertAdjacentHTML('beforeend', newFormHtml); i += 1; document.getElementById('noOfYears').value = parseInt(document.getElementById('noOfYears').value) + 1 // 只初始化刚插入的最后一个表单块里的select,不处理旧的 initSelect(targetDiv.lastElementChild); } function initSelect(container = document) { // 查找容器内未初始化的select,加tomselect-initialized标记避免重复绑定 var selectList = container.querySelectorAll('.js-select:not(.tomselect-initialized)'); for (var v = 0; v < selectList.length; v++) { try { new TomSelect(selectList[v], { render: { 'option': function(data, escape) { return data.optionTemplate || `<div>${data.text}</div>>` }, 'item': function(data, escape) { return data.optionTemplate || `<div>${data.text}</div>>` } } }) // 初始化完成加标记类 selectList[v].classList.add('tomselect-initialized'); } catch (err) { console.log(err) } } } // 页面加载初始化第一个表单 GetYear();
如果项目里有其他逻辑依赖原来的DOM结构,只需要保留
insertAdjacentHTML替换innerHTML、给初始化完的select加标记类、只初始化新表单内select这三个核心修改即可,id重复问题可以后续逐步调整。
内容的提问来源于stack exchange,提问作者Nawaf
相关产品推荐
相关产品推荐

