记一个 event.target 类型导致的 Bug

今天刚接的新锅报了个不太好复现的 Bug(实际上到最后都没有在原环境复现……),于是看了一波代码找问题,Bug 大致是:

xxx.hasAttribute is undefined

调用到的地方有几处,首先确定了 hasAttribute 是写在 Element 的原型链上的,即如果是 Element 类型(element.nodeType === 1),那么必然有 hasAttribute。

在此之前,我先查了一下,什么情况下 「Element」 会没有 hasAttribute:

很巧的发现了:https://stackoverflow.com/questions/10819230/element-has-no-method-hasattribute-why

上面提到了 document,所以很快就怀疑什么地方传入的 document

嫌疑人有两处,之后通过报错的信息(尽管是被压缩过的,看到眼残)……发现是一个 on('keydown', fun) 报的错。

监听传入的可能不是 Element 吗?——从上面那个问题来看是可能的,但是怎么复现?

于是我操作了一波,即使把 body 删完,至少也会触发到 html 这个 Element,而不是 #document

不死心,看了 MDN 的手册之后发现 keydown 中的手册写了 Target 的情况有 Document 或者 Element,也就是确实有可能。

可是我又确实复现不了,此外,W3 在 keydown 中的 Trusted Targets 为 Element,对于 Event.target 字段是这么解释的,大致意思是 聚焦的元素 -> body -> root element:

Event.target : focused element processing the key event or if no element focused, then the body element if available, otherwise the root element

root element: The first element node of a document, of which all other elements are children. The document element.

Well,实际上再不济也会回到的节点其实是第一个元素(通常就是 html 了),依旧是个元素,符合我多次尝试的结果:

顺着 W3 查,我们找到了 https://dom.spec.whatwg.org/#dispatching-events,在步骤二里规定了:

Let targetOverride be target, if legacy target override flag is not given, and target’s associated Document otherwise.

英文不太好……其实没怎么看懂,但似乎和 Document 有点关系,正巧之后看到了 event.isTrusted 属性:

isTrusted is a convenience that indicates whether an event is dispatched by the user agent (as opposed to using dispatchEvent()). The sole legacy exception is click(), which causes the user agent to dispatch an event whose isTrusted attribute is initialized to false.

大致是说可以判断是不是由用户触发的事件,回忆杀一波 W3 写的也是 Trusted Targets,是不是意味着指的是正常触发的情况,于是我就想到了模拟事件:

document.dispatchEvent(new KeyboardEvent('keydown',{'key':'a'}));

成功触发得到了 Document 而不是 Element,从这点上来说,每个手册都没错,但是触发条件确实有点苛刻,Bug 在原场景正常使用中也没有能复现,如果是其他库的干扰报错确实应该多几层调用栈信息……所以还是一个挺诡异的问题,但至少至此,终于确定了 event.target 真的可以是 Document,可喜可贺,可喜可贺。

需要用到 Element 特有的方法时还是应该判断一波 nodeType。

// 于是一天就修了一个 Bug……一行代码……

植入部分

如果您觉得文章不错,可以通过赞助支持我。

如果您不希望打赏,也可以通过关闭广告屏蔽插件的形式帮助网站运作。

标签: 知识, 语法

已有 5 条评论

  1. Mizuka

    不好复现的bug能修掉花一天也值了

  2. towry

    能挖的这么深,真的很不错了

  3. 鬼柒

    前面都对,但是isTrusted 应该不是原因。
    确实 如果e.target 是document 会导致没有hasAttribute 方法
    但是原因就在这里了,并非模拟事件导致target成了document。
    而是你用document 作为触发源 模拟了事件。

    document.dispatchEvent(new KeyboardEvent('keydown',{'key':'a'}));

    看下面的例子 打开console看一下就知道了。

    http://runjs.cn/code/vpcczmuq

    所以其实最根本的问题是 .on("keydown",fn) 这个.on 前面的东西是document导致的target成了document。排除掉触发源为document 应该可行,需要进一步的源代码细节才能排查了。

    1. on 是监听(封装的兼容的 addListener)而不是模拟触发,我没有说 isTrust 是原因啊,只是在正常条件下我实在没法复现。

      你的栗子跟我构造的栗子不是一样的吗……

      1. 鬼柒

        是了,我理解有误。原来你说isTrust是为了引出模拟事件这件事。我以为模拟事件是“常识”,反而把isTrust这件事顺着推导成你说的“原因”了。

添加新评论