JavaScript中动态创建的父元素事件可触发,子元素为何不行?
手风琴组件子元素事件绑定失效问题排查
问题场景
动态创建手风琴组件,要求同一时间仅展开一个项,点击标题触发展开/切换逻辑。为动态生成的标题div绑定click事件后无法触发,但为其父元素item绑定同一事件却能正常执行。
相关代码
function createInfo() { const info = document.querySelector('#info'); const accordion = document.createElement('div'); accordion('class', 'accordion'); // 注:此处应为accordion.setAttribute('class', 'accordion'),原代码存在语法错误 const accordionItemList = [...]; for (const itemObject of accordionItemList) { const item = document.createElement('div'); item.setAttribute('class', 'accordion-item'); const title = document.createElement('div'); title.setAttribute('class', 'accordion-title'); title.innerHTML = `<p>${itemObject.title}</p><div class="arrow">`; item.appendChild(title); item.innerHTML += `<div class="accordion-text">${itemObject.text}</div>`; accordion.appendChild(item); // 此事件无法触发 title.addEventListener('click', () => { handleExpand(item, accordion.children); }); // 此事件可正常触发 item.addEventListener('click', () => { handleExpand(item, accordion.children); }); } info.appendChild(accordion); }
问题原因
核心问题出在item.innerHTML +=
${itemObject.text}
``这一行:- 当使用
innerHTML +=时,浏览器会先将item当前的所有内容(包括已经通过appendChild添加的title元素)转换成HTML字符串,拼接上新内容后重新解析,生成全新的DOM节点替换掉item原来的子元素。 - 原来绑定了事件的title元素被销毁,新生成的title节点是完全不同的DOM对象,之前绑定的
click事件自然失效。 - 而item元素本身是始终存在的DOM节点,没有被替换,因此它的事件绑定不受影响。
解决办法
有两种可行的修复方式:
方式一:先完成所有DOM拼接,再绑定事件
将事件绑定代码移到item.innerHTML +=之后,操作重新生成后的title节点:
for (const itemObject of accordionItemList) { const item = document.createElement('div'); item.setAttribute('class', 'accordion-item'); item.innerHTML = `<div class="accordion-title"><p>${itemObject.title}</p><div class="arrow"></div></div>` + `<div class="accordion-text">${itemObject.text}</div>`; accordion.appendChild(item); // 此时获取的是重新生成后的title节点 const title = item.querySelector('.accordion-title'); title.addEventListener('click', () => { handleExpand(item, accordion.children); }); item.addEventListener('click', () => { handleExpand(item, accordion.children); }); }
方式二:用appendChild代替innerHTML +=添加文本区域
避免重新解析整个item的内容,保留原来的title节点:
for (const itemObject of accordionItemList) { const item = document.createElement('div'); item.setAttribute('class', 'accordion-item'); const title = document.createElement('div'); title.setAttribute('class', 'accordion-title'); title.innerHTML = `<p>${itemObject.title}</p><div class="arrow"></div>`; item.appendChild(title); // 用createElement和appendChild添加文本区域,不破坏原有DOM结构 const textDiv = document.createElement('div'); textDiv.setAttribute('class', 'accordion-text'); textDiv.textContent = itemObject.text; // 若text含HTML则用innerHTML item.appendChild(textDiv); accordion.appendChild(item); // 此时title节点未被替换,事件绑定有效 title.addEventListener('click', () => { handleExpand(item, accordion.children); }); item.addEventListener('click', () => { handleExpand(item, accordion.children); }); }
另外注意原代码中accordion('class', 'accordion')是语法错误,应改为accordion.setAttribute('class', 'accordion')或accordion.className = 'accordion'。
内容的提问来源于stack exchange,提问作者Ahmet C. Buyukyilmaz
相关产品推荐
相关产品推荐

