函数内循环中classList.add正常,setTimeout移除时classList为null报错
问题现象:setTimeout未按预期工作,classList.add功能正常,但添加setTimeout执行classList.remove时,控制台报错“VM1578:5 Uncaught TypeError: Cannot read property 'classList' of null”。
你的问题代码如下:
var clrArr = ["red", "blue", "yellow", "green"]; var randomClr = []; //随机颜色生成 function noGen() { return Math.floor(Math.random()*4); } function randomClrGen() { var no = noGen(); randomClr.push(clrArr[no]); console.log(randomClr); } function randomBtnPress() { randomClrGen(); for(var i = 0; i<randomClr.length; i++){ document.querySelector("."+ randomClr[i]).classList.add("pressed"); setTimeout(function () { document.querySelector("."+ randomClr[i]).classList.remove("pressed"); },100); }; }
问题根源
这个报错的核心是闭包带来的变量绑定陷阱。你用var声明的循环变量i是函数作用域的,整个循环里只有一个i的实例。当setTimeout的回调函数在100ms后执行时,循环早就跑完了,此时i的值已经变成了randomClr.length(超出了数组的合法索引范围),所以randomClr[i]会返回undefined。用document.querySelector("." + undefined)去查找元素,自然找不到任何东西,返回null,再去访问null的classList属性,就会抛出你看到的类型错误。
解决方案
这里有几种简单的修复方式,你可以根据自己的环境选择:
1. 用let声明循环变量(推荐,ES6+)
let是块级作用域的变量,在每次循环迭代时都会创建一个新的i绑定,回调函数会捕获当前迭代的那个i的值,不会被后续循环影响:
function randomBtnPress() { randomClrGen(); // 把var改成let for(let i = 0; i<randomClr.length; i++){ const btn = document.querySelector("."+ randomClr[i]); btn.classList.add("pressed"); setTimeout(function () { btn.classList.remove("pressed"); },100); }; }
我顺便把DOM元素的查询提前了,避免在回调里重复查询DOM,性能会更好一点。
2. 用立即执行函数(IIFE)捕获迭代值(兼容旧浏览器)
如果需要兼容不支持ES6的旧环境,可以用IIFE把每次循环的i值封装到独立的作用域里:
function randomBtnPress() { randomClrGen(); for(var i = 0; i<randomClr.length; i++){ // 用IIFE捕获当前的i值 (function(currentIdx) { const btn = document.querySelector("."+ randomClr[currentIdx]); btn.classList.add("pressed"); setTimeout(function () { btn.classList.remove("pressed"); },100); })(i); }; }
3. 用forEach遍历数组(更简洁)
既然你是在遍历randomClr数组,直接用forEach会更直观,也不会有闭包问题:
function randomBtnPress() { randomClrGen(); randomClr.forEach(color => { const btn = document.querySelector("." + color); btn.classList.add("pressed"); setTimeout(() => { btn.classList.remove("pressed"); }, 100); }); }
这种写法代码更简洁,也避开了循环变量的陷阱,推荐使用。
内容的提问来源于stack exchange,提问作者AJMAL KAREEM

