If state changed is true, fire a trusted event with the name popstate at the Window object of the Document, using the PopStateEvent interface, with the state attribute initialized to the value of state. This event must bubble but not be cancelable and has no default action.
所以popState 是否触发的本质是,state changed 这个变量是否为 true。
那这个变量怎么判断的呢,我们可以看到第 12 步
12 . Let state changed be true if the Document of the specified entry has a latest entry, and that entry is not the specified entry; otherwise let it be false.
这个 latest entry 是什么东西呢,我们看看第13步。
Let the latest entry of the Document of the specified entry be the specified entry.
Each Document in a browsing context can also have a latest entry. This is the entry or that Document that was most the recently traversed to. When a Document is created, it initially has no latest entry.
问题分析
结合这两句我们可以知道。
综合以上规范我们可以得知到以下几种情形。
1) 进入一个新页面A,再跳转到新页面B,然后点击回退。
因为A页面没有进行任何的历史遍历,所有没有推入任何 history entry。故,这个 document A上没有 latest entry 存在。因此 state changed 是 false。所以不会执行 popState。
2) 进入新页面 A ,利用 replaceState更换当前的历史栈,再跳转到页面 B,然后点击回退。
问题所在
这几天弄一个需求,需要监听用户的回退操作。马上就想起了
popstate
.但是在多页应用里,每一页的回退都没有触发 popState 事件。
查阅规范
这是为什么呢,我们翻一下规范。
首先我们查一下
history.go
做了些什么。可以看到他是调用了一个
joint session history
方法。于是我们直接跳到 joint session history 章节,可以看到有
To traverse the history by a delta delta
的步骤描述。这里我们可以获取到几个有用的定义:
所以
specified entry
就是我们想要前往的页面。在规范的 5.6.10 的章节我们可以查到相关的历史遍历步骤。
接着我们在第六步的第三小步发现需要执行浏览器的历史遍历,我们继续探查
通过history traversal跳转到目的地。
在第14步的第一小步骤,我们终于发现了 popState 的身影:
所以popState 是否触发的本质是,
state changed
这个变量是否为true
。那这个变量怎么判断的呢,我们可以看到第 12 步
这个 latest entry 是什么东西呢,我们看看第13步。
就是每次遍历的时候推入的最新堆栈。另外latest entry我们可以看到:
问题分析
结合这两句我们可以知道。
综合以上规范我们可以得知到以下几种情形。
1) 进入一个新页面A,再跳转到新页面B,然后点击回退。
因为A页面没有进行任何的历史遍历,所有没有推入任何 history entry。故,这个 document A上没有 latest entry 存在。因此
state changed
是false
。所以不会执行 popState。2) 进入新页面 A ,利用 replaceState更换当前的历史栈,再跳转到页面 B,然后点击回退。
此时回退到页面 A,A 存在 latest entry, 但是 latest entry 和 specified entry 是一样的,所以不会触发变化。
3) 进入新页面 A,利用 pushState 填入 A 的历史条目 a,然后回退。
因为 latest entry 是 a,且和 specified entry 不等。所以会触发 popState 事件,虽然 history 有回退,但是页面没有变化。
解决方案
因此如果我们要监听用户的回退操作,应该主动 push 一个新的 state。
那么当用户点击回退时,该 state 会弹出,触发 popState 事件,此时我们获得了用户后退行为,再相应的帮助用户回退即可。
当然这样会带来一些麻烦,就是主动回退时要多回退一层,还有理清楚所有层数的关系。
可以考虑封装一个小工具出来。