踩坑描述:
前一段时间我接触到了一个可以在屏幕上触控的五点印章,其原理与手写笔相似,是用于在活动场所中给游客在屏幕上进行盖章打卡。盖章识别的逻辑比较简单,主要是通过监听屏幕触摸事件,获取每个触摸点的位置信息与给定的初始坐标进行判断图形是否相似(这部分由GPT完成了)返回一个结果值。由于对这个效果比较好奇,于是我也尝试复现这个效果。
印章上一共有五个点,于是当触发屏幕触摸事件时,我设置当触摸点数组的数量达到5时才开始判断图形是否相似,也就是需要五根手指用时按在屏幕上,将结果使用
window.alert
进行反馈。正是因为这个alert
,导致一个奇怪的问题发生了:当判断的结果为不匹配时,下一次不论使用多少根手指,都会触发判断事件
复现Demo
以下是对问题的一个简单复现,当执行这段代码时,一开始使用三指进行点击,会触发alert
弹窗,后续不论使用多少手指都会触发弹窗
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>alert异常执行</title>
</head>
<body style="width: 100vw; height: 100vh">
<script>
document.body.addEventListener('touchstart', (e) => {
if (e.touches.length === 3) {
alert('事件触发')
}
})
</script>
</body>
</html>
思考发现
一开始我并没有怀疑这个alert,而是反复检查其他的逻辑,后来经过了各种尝试,比如将保存触摸点位的数组在每次判断完后进行清空,或者是添加触摸结束事件再对数组进行清空,又或者是移除事件并再次添加等,但都于事无补。
直到我将alert
替换为了其他的验证方式,发现这个bug
竟然消失了,带着疑惑的心情,我开始查询有关alert
的资料,并且询问GPT是什么原因造成了这个原因,得到的大致结果如下:
原因
alert()
是一个 同步阻塞 的函数调用。当调用alert()
时,它会打开一个弹出框并暂停 JavaScript 执行,直到用户关闭弹出框。在这段时间内,JavaScript 执行栈和事件循环被阻塞,浏览器无法处理其他事件或任务- 在
alert
弹出期间,浏览器可能会发生一些事件丢失或者队列被阻塞的情况。当关闭alert
后,浏览器会恢复事件处理,但可能会由于事件队列的顺序问题,导致后续的触摸事件被错误地触发。 - 当触摸触发
alert
后,由于alert
阻塞了事件处理,在弹出框关闭之前,浏览器的事件队列并未完全处理。可能导致后续的touchstart
事件被错误地认为是符合触发条件的,因此再次执行alert
,即使触摸点数不为5。 - 更具体地,
touchstart
事件会被重入(再次触发),由于事件处理机制和浏览器的事件循环被打断,接下来的触摸事件可能不会按照预期被区分。
参考内容: 为什么alert()调用会导致我的事件再次被触发?
总结
alert() 引起的事件触发异常行为是由于它是同步阻塞的,导致浏览器的事件循环暂停,从而阻碍了触摸事件的正常处理。
浏览器无法及时处理新的触摸事件,从而出现错误的事件触发行为。
为了避免这种问题,应该避免在事件回调中使用 alert(),使用非阻塞的调试方法如 console.log() 或者通过 setTimeout 延时执行处理逻辑。
写在后面:
看来以后还是不要为了图方便而轻易使用这类会出现问题的方法,该下功夫的地方还是少点偷懒。
上一次的文章截止到目前只有几十的播放量,不过也在我意料之内,趁年轻多尝试多试错总归也是好的,最重要的是要保持行动力,专注在道路上!