修复移动端Audio API中Uncaught TypeError: addEventListener空指针错误
音频播放器开发错误解决指南
遇到的问题
我跟着视频开发一款可嵌入音乐网站的移动端带播放列表音频播放器,在对应代码位置遇到两个问题:
- 运行时报错:
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener') - JS验证器提示:
Functions declared within loops referencing an outer scoped variable may lead to confusing semantics. (liAudioTag)
我并非JavaScript专家,正在学习阶段,求解决方法,相关代码如下:
const ulTag = wrapper.querySelector("ul");//1:07:10 for (let i = 0; i < allMusic.length; i++) { let liTag = `<li> <div class="row"> <span>${allMusic[i].name}</span> <p>${allMusic[i].artist}</p> </div> <audio class="${allMusic[i].src}" src="media/${allMusic[i].src}.mp3"></audio> <span id="${allMusic[i].src}" class="audio-duration">3:40</span> </li>`; ulTag.insertAdjacentHTML("beforeend", liTag);//1:08:28 let liAudioDuaration = ulTag.querySelector(`#${allMusic[i].src}`);//1:10:34 let liAudioTag = ulTag.querySelector(`.${allMusic[i].src}`);//1:11:29 liAudioTag.addEventListener("loadeddata", ()=>{ let audioDuration = liAudioTag.duration; let totalMin = Math.floor(audioDuration / 60); let totalSec = Math.floor(audioDuration % 60); if (totalSec < 10){//add 0 if sec less then 10 totalSec = `0${totalSec}`; } liAudioDuaration.innerText = `${totalMin}:${totalSec}`; }); }
问题原因及解决方案
1. Cannot read properties of null 错误原因及修复
原因:
- 用
allMusic[i].src作为audio的类名和span的ID,如果src值包含空格、斜杠、特殊符号(比如my song.mp3),会导致CSS选择器失效,querySelector找不到元素返回null,调用addEventListener就会报错。 - 另外,
insertAdjacentHTML插入后DOM可能未完全更新,全局查询ul内元素可能出现延迟。
修复方案:
- 改用循环索引生成唯一、安全的ID,避免使用可能含特殊字符的src值;
- 插入li后,直接从刚添加的li元素内查询子节点,而非从整个ul全局查询,结果更可靠。
2. 循环内函数引用外部变量的语义警告修复
原因:
虽然用let声明变量,但箭头函数仍会引用外部的liAudioTag变量,循环迭代时变量指向会变化,可能导致后续事件触发时拿到的不是预期的audio元素。
修复方案:
在事件回调中用event.target获取当前触发事件的audio元素,彻底避免引用外部循环变量。
修正后的完整代码
const ulTag = wrapper.querySelector("ul"); for (let i = 0; i < allMusic.length; i++) { // 取出当前循环的音乐数据,简化代码 const currentMusic = allMusic[i]; // 用循环索引生成唯一安全的ID,避免特殊字符问题 const audioId = `audio-item-${i}`; let liTag = `<li> <div class="row"> <span>${currentMusic.name}</span> <p>${currentMusic.artist}</p> </div> <audio id="${audioId}" src="media/${currentMusic.src}.mp3"></audio> <span class="audio-duration">3:40</span> </li>`; ulTag.insertAdjacentHTML("beforeend", liTag); // 获取刚插入的最后一个li元素 const latestLi = ulTag.lastElementChild; // 从当前li内查询子元素,精准可靠 const liAudioTag = latestLi.querySelector(`#${audioId}`); const liAudioDuration = latestLi.querySelector(".audio-duration"); liAudioTag.addEventListener("loadeddata", (event) => { // 通过event.target获取当前触发事件的audio元素 const audioElement = event.target; const audioDuration = audioElement.duration; const totalMin = Math.floor(audioDuration / 60); let totalSec = Math.floor(audioDuration % 60); if (totalSec < 10){ totalSec = `0${totalSec}`; } liAudioDuration.innerText = `${totalMin}:${totalSec}`; }); }
修改说明
- 用
audio-item-${i}作为audio的唯一ID,彻底避免src特殊字符导致的选择器失效问题; - 插入li后通过
ulTag.lastElementChild获取刚添加的节点,再从该节点内查询子元素,避免全局查询的不确定性; - 事件回调中使用
event.target获取当前音频元素,不再依赖外部循环变量,解决语义警告问题; - 提取
currentMusic变量,简化代码可读性。
内容的提问来源于stack exchange,提问作者mim
相关产品推荐
相关产品推荐

