window.onpopstate, event.state == null?

48

我在Stack Overflow上阅读了几篇问答并通过Google查找了问题,但似乎无法获得除null之外的event.state值。

我明白在使用jQuery事件时它无法工作,但是当我这样做时:

window.onpopstate = function (e) {
     console.log(e.state)
}

通过类似jQuery.ajax成功的调用

history.pushState({ lastUrl: data.State }, data.Title, data.UrlKey);

Chrome(v19)和Firefox(v12)似乎都只返回null,我是否漏掉了什么?这是因为它们还没有完全实现吗?


可能是HTML历史状态在OS X Chrome 10.0.648.151上的问题的重复。 - Derek 朕會功夫
5
@ Derek,你提供的问题涉及到在页面加载时触发popstate事件。为了澄清,无论何时popstate被触发,从pushState函数发送的任何数据(例如上面示例中的lastUrl)都不会在事件对象中可用,这就是我正在询问的问题。 - mihok
7个回答

68

e.state 指的是已经推送的倒数第二个状态。为了让 e.state 不为空,您至少需要推送状态两次。这是因为您应该在首次加载站点时保存状态,之后每当状态更改时都要保存。


1
我解决了我在这里提出的问题http://stackoverflow.com/questions/13107147/javascript-history-state-incorrect...如果你能在这里为我的问题写一个答案,我可以接受它。 - Rohit Banga
5
总体评论:我建议不要对函数window.onpopstate = function(e){}进行过度加载,而是简单地“监听”它window.addEventListener("popstate", function(event) {})... 因为否则你会取消其他实现它的代码,而你并没有重写它们。注意:本翻译保持原文结构和含义,力求通俗易懂,没有加入解释或个人观点。 - Ricky Levi
1
请看下面我对同样问题的描述。每次用户(即我)点击链接时,我都调用了pushState多次。但状态仍然始终为空。只有在ONLOAD处理程序中至少调用两次pushState()时,它才变为非空。我不明白为什么从onclick处理程序中多次调用它没有使其起作用。也许第三个参数传递了错误的值?谁知道呢。但在onload处理程序中调用pushState()两次解决了这个问题。 - Panu Logic
2
仅作为对上面答案的补充:我更喜欢在加载时调用replaceState来设置所需状态,而不是调用两次pushState,这样会向历史记录中添加2个条目。然后,后续的pushState调用将按预期工作。 - Peleg

19

我认为这个问题需要更清晰的解释,因为其他答案中的“second last state that was pushed”可能会引起困惑。

当我们执行history.pushState时,我们将一个新状态推入历史堆栈中,并成为当前状态(如我们可以从导航栏URL中看到的那样)。

window.onpopstate事件意味着顶部历史状态正在被弹出(从堆栈中取走),因此e.state现在将指向堆栈中的新顶部状态(因为导航栏将指向先前的URL)。


4
我为这个问题苦苦挣扎了很多小时,太多小时了。即使我之前调用了pushState(),当我点击后退按钮时,onpopstate处理程序的状态参数仍为空。
我还观察到,我的onclick处理程序调用pushState()会导致onpopstate处理程序被触发。根据我在网上阅读的内容,我认为只有用户点击后退按钮才应该调用onpopstate处理程序。但在我的情况下似乎不是这样。
浏览器可能存在漏洞吗?这似乎不太可能,因为我在Chrome和FireFox上都遇到了相同或类似的问题。但也有可能。或者规范中存在“错误”,难以正确实现。没有公共单元测试显示应该如何工作。
最后,在我的onload处理程序中加入了这两个调用后,一切都开始正常工作了。
pushState (myInitialState, null, href);
pushState (myInitialState, null, href);

因此,在onload处理程序中,我必须两次进行相同的push-state()调用!之后,我的onpopstate处理程序开始接收到状态不为null而是先前作为参数传递给pushState()的状态。

我真的不明白为什么现在它能够工作,而当我只调用一次pushState时它为什么不能工作。

我想理解为什么它现在可以工作,但我已经花了太多时间使它工作。如果有人有在线讲解的良好示例代码的参考资料,那就太棒了。


在我的情况下,popState 经常会接收到 null 状态(并非总是如此)。确保有额外的初始 pushState 调用似乎已经解决了这个问题。 - Ujjwal Singh

1

如果您曾经学习过撤销/重做堆栈,那么下面的图片会很有帮助。

enter image description here

事实上,历史记录是以这样的方式保存在堆栈中。您当前的状态来自当前堆栈项,并假设有一个指针指向它; 当您调用history.back()时,指针会移动到堆栈的旧项,顺便说一下,onpopstate事件的状态与history.state相同。您将只获取指针当前指向的项,该项由history.replaceState修改或由history.pushState添加。

这个过程也解释了为什么history.length不会改变,因为它表示整个堆栈的长度。当您调用history.go(1)时,指针会移动到较新的项。

当指针位于堆栈的中间位置时,调用history.pushState将导致弹出指针上面的所有项目,并添加新项目,还可以看到history'length发生变化。


2
这句话如何解释“null”值呢? - Martynas Jusevičius

1

哇,这里所有的答案都非常令人困惑。

  • 你需要推送或替换初始状态。
  • popstate不会给你第二个最后状态,而是最后一个状态。
  • 当然没有必要两次推送初始状态。

我认为让每个人感到困惑的是,event.state将保存您在history.pushState()调用中设置为第一个(data)参数的任何值。如果将其设置为null,那么确实您的event.state也将为null。

为什么不需要推送初始状态?

初始状态就在您的事件中,只是没有设置data,因此event.state将为null。您要寻找的是重定向回初始页面的document.location(因为在popstate时,我们已经处于该位置)。

如果您想要完全控制,最好用一些有意义的data集合替换初始状态,这样您就可以确定它是您预期的状态,而无需比较文档url。


1

history.state与当前页面相关联,因此可以进行前进后退。要获取第一个历史状态,您可以使用history.replaceState将其放置在第一个历史状态中。

window.history.replaceState(currentState, document.title)

0
如Jaco Briers所述,为了使popstate按我们认为的方式工作,我们需要首先存储初始状态以便在单击浏览器的后退按钮时返回。这是一个使用jQuery的示例实现:

示例代码

$(window).on({
  // Store initial state
  'load': function() {
    window.history.pushState({
      'html': '<div id="ajax-content">' + $('#ajax-content').html() + '</div>'
    }, '', document.URL);
  },
  // Handle browser Back/Forward button
  'popstate': function(e) {
    var oState = e.originalEvent.state;
    if (oState) {
      $('#ajax-content').replaceWith(oState.html);
    }
  }
});

...

// Update AJAX content
var sUrl = 'https://www.example.com/category?id=123&format=ajax';
$.ajax({
  type: 'GET',
  url: sUrl,
  success: function(sHtml) {
    $('#ajax-content').replaceWith(sHtml);
    window.history.pushState({
      'html': sHtml
    }, '', sUrl);
  }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接