window与document绑定scroll事件的差异及触发冲突问题
$(window).on('scroll')和$(document).on('scroll')会出现事件冲突? 这两种写法确实存在关键差异,核心根源在于jQuery对document的scroll事件的特殊兼容处理,以及return false在jQuery事件处理器中的“超强阻断”作用——这正是你遇到的现象的原因。
先搞懂事件绑定的本质
在原生JS里,全局页面滚动时,只有window对象会触发scroll事件(除非你给document或其他元素加了overflow: scroll让它自己能滚动)。jQuery为了让开发者写代码更直观,做了一层兼容:
- 当你写
$(window).on('scroll', handler),事件直接绑定在window上,逻辑清晰直接。 - 当你写
$(document).on('scroll', handler),jQuery会偷偷把这个事件绑定到window上——因为它知道document本身不会触发全局滚动的scroll事件,这么做是为了让开发者的写法更符合直觉。
所以你绑定的这两个事件处理器,最终都挂在了同一个window对象上,执行顺序完全按照你绑定的先后顺序来:先绑定的document处理器先运行,然后才是window的处理器。
再看return false的“破坏力”
在jQuery的事件处理器里,return false不是简单的退出函数——它等价于同时执行三个操作:
event.preventDefault():阻止事件的默认行为(不过scroll事件其实没有什么默认行为需要阻止)event.stopPropagation():阻止事件冒泡(这里也没用,因为已经到最顶层的window了)event.stopImmediatePropagation():这才是关键!它会阻止当前元素上所有后续绑定的事件处理器执行
这就完美解释了你看到的现象:当滚动距离超过300px时,document的处理器执行return false,其中的stopImmediatePropagation()直接掐断了后续window处理器的执行,所以scroll2的计数就停住了。
为什么换成两个$(document).on('scroll')就“没冲突”?
其实严格来说,这时候依然会有同样的阻断效果——如果第一个处理器执行return false,第二个也会被阻止。可能你测试时的场景有细微调整?或者你指的是命名空间的问题?但要注意:命名空间不会绕过stopImmediatePropagation()的限制,它针对的是同一元素上的所有事件处理器,不管有没有命名空间。
怎么解决这个问题?
如果你想保留两个事件处理器,同时控制document处理器的逻辑,别用return false,改用普通的return退出函数就行:
$(document).on('scroll', function () { updateCount($scroll1); if ($(this).scrollTop() > 300) { // 只是退出当前函数,不阻断后续事件处理器 return; } });
这样window的处理器就能正常触发,计数也会继续增长了。
内容的提问来源于stack exchange,提问作者lesssugar

