PAYATHON 2026

for 循环中为何出现 Cannot read properties of undefined 错误?

排障赵师傅

报错是因为所有事件处理函数共享同一个 var i。鼠标移入图片时,循环已经结束,i 等于 heads.length,此时 heads[i] 为 undefined,访问它的 .style 就会报错。

最小的改动是把 var i 改成 let i。也可以在事件处理函数中使用 event.currentTarget,直接获取绑定监听器的元素。

为什么绑定成功,触发事件时却报错?

调用下面的 addEventListener() 时,循环还在进行,i 是有效索引,所以能正常添加监听器:

heads[i].addEventListener("mouseover", function () {
  heads[i].style.animation = "colorIn 2s";
});

传入的回调函数要等事件触发后才执行,其中的 heads[i] 也要到那时才求值。注册监听器时,回调并没有保存对应的图片。

用 var 声明的 i 属于 main() 的函数作用域,每轮循环创建的回调都通过闭包访问同一个 i。题目中有 8 张图片,假设集合没有变化:

  • 注册监听器时,i 依次为 0 到 7。
  • 循环结束时,i 为 8。
  • 鼠标移入任意图片后,回调访问的都是 heads[8].style。

集合最后一个有效索引是 7,因此 heads[8] 返回 undefined。错误信息中的 reading 'style' 就是指这次属性访问。

方法一:把 var 改为 let

在 for 循环中,用 let 声明的变量在每轮迭代中都有独立的绑定,回调因此能保留各自那一轮的索引。

将 main.js 替换为:

function main() {
  const heads = document.getElementsByClassName("heads");

  for (let i = 0; i < heads.length; i++) {
    heads[i].addEventListener("mouseover", function () {
      heads[i].style.animation = "colorIn 2s";
      heads[i].style.filter = "none";
    });
  }
}

原来的 <body onload="main()"> 可以继续使用。对于题目中这些固定的图片,这样就能解决报错。

方法二:通过 event.currentTarget 获取对应元素

如果回调只需要修改绑定监听器的图片,可以直接使用 event.currentTarget,触发事件时就不必再通过索引查找元素:

function main() {
  const heads = document.getElementsByClassName("heads");

  for (const head of heads) {
    head.addEventListener("mouseover", function (event) {
      const element = event.currentTarget;

      element.style.animation = "colorIn 2s";
      element.style.filter = "none";
    });
  }
}

浏览器调用监听函数时,会把事件对象作为参数传入。所以,function (event) 接收的是事件对象,即使把参数名改成 head 或 element,浏览器传入的也仍然是事件对象。

event.currentTarget 是当前正在执行的监听器所绑定的元素。这里的监听器直接绑定在每张图片上,因此它就是要修改的图片。

event.target 是事件最初发生的目标。如果以后把监听器绑定到包含子元素的容器上,两者可能不同。要修改监听器绑定元素的样式,使用 currentTarget 更直接。

注意事项

  • 原代码中的 heads = ... 没有声明变量。在非严格模式下,这可能意外创建全局变量;在严格模式下则会抛出 ReferenceError。应使用 const heads = ...。
  • getElementsByClassName() 返回动态的 HTMLCollection。之后如果增删元素或修改相关类名,集合内容和索引可能发生变化。let 能解决循环变量共享的问题;使用 event.currentTarget 则可以直接访问绑定元素,避免依赖事件触发时集合中的索引。
  • 在传给 addEventListener() 的普通函数监听器中,也可以用 this.style 访问绑定元素。箭头函数没有自己的 this,不能直接照搬这种写法。
  • 这个异常发生在读取 .style 时,修改 CSS 无法解决索引越界问题。重复设置相同的 animation 值通常也不会重新播放动画。如果需要每次鼠标移入都重播,还要另行处理动画重置。

备注:内容仅供参考。