pushState创建了重复的历史记录条目并覆盖之前的条目

17
我创建了一个Web应用程序,利用history pushStatereplaceState方法来在更新历史记录的同时导航页面。
脚本本身几乎完美地工作;它可以正确加载页面,并在需要时抛出页面错误。然而,我注意到一个奇怪的问题,即pushState会将多个重复的条目(并在之前替换条目)推入历史记录中。
例如,假设我按以下顺序操作:
1. 加载index.php(历史记录将为:Index) 2. 转到profile.php(历史记录将为:Profile、Index) 3. 转到search.php(历史记录将为:Search、Search、Index) 4. 转到dashboard.php 最后,我的历史记录将显示如下(按最近到最旧排序):
Dashboard Dashboard Dashboard Search Index
这样做的问题是,当用户单击向前或向后按钮时,他们要么被重定向到错误的页面,要么必须单击多次才能返回一次。此外,如果他们去检查他们的历史记录,这也没有任何意义。
这是我目前的进展:
var Traveller = function(){
    this._initialised = false;

    this._pageData = null;
    this._pageRequest = null;

    this._history = [];
    this._currentPath = null;
    this.abort = function(){
        if(this._pageRequest){
            this._pageRequest.abort();
        }
    };
    // initialise traveller (call replaceState on load instead of pushState)
    return this.init();
};

/*1*/Traveller.prototype.init = function(){
    // get full pathname and request the relevant page to load up
    this._initialLoadPath = (window.location.pathname + window.location.search);
    this.send(this._initialLoadPath);
};
/*2*/Traveller.prototype.send = function(path){
    this._currentPath = path.replace(/^\/+|\/+$/g, "");

    // abort any running requests to prevent multiple
    // pages from being loaded into the DOM
    this.abort();

    return this._pageRequest = _ajax({
        url: path,
        dataType: "json",
        success: function(response){
            // render the page to the dom using the json data returned
            // (this part has been skipped in the render method as it
            // doesn't involve manipulating the history object at all
            window.Traveller.render(response);
        }
    });
};
/*3*/Traveller.prototype.render = function(data){
    this._pageData = data;
    this.updateHistory();
};
/*4*/Traveller.prototype.updateHistory = function(){
    /* example _pageData would be:
    {
        "page": {
            "title": "This is a title",
            "styles": [ "stylea.css", "styleb.css" ],
            "scripts": [ "scripta.js", "scriptb.js" ]
        }
    }
    */
    var state = this._pageData;
    if(!this._initialised){
        window.history.replaceState(state, state.title, "/" + this._currentPath);
        this._initialised = true;
    } else {
        window.history.pushState(state, state.title, "/" + this._currentPath);  
    }
    document.title = state.title;
};

Traveller.prototype.redirect = function(href){
    this.send(href);
};

// initialise traveller
window.Traveller = new Traveller();

document.addEventListener("click", function(event){
    if(event.target.tagName === "a"){
        var link = event.target;
        if(link.target !== "_blank" && link.href !== "#"){
            event.preventDefault();
            // example link would be /profile.php
            window.Traveller.redirect(link.href);
        }
    }
});

非常感谢您的帮助,
万分感激。


1
嗯,我并不确定,所以在评论中写下来了。我看到你在updateHistory函数中添加了浏览器历史记录的内容。现在,updateHistory可能会被调用两次,第一次是当你初始化旅行者(window.Traveller = new Traveller();constructor -> init -> send -> render -> updateHistory)时,然后也会通过click事件监听器中的redirect调用。我还没有测试过,只是猜测,所以将其添加为评论而不是答案。 - Akshit Arora
@AkshitArora 这是正确的。每当有人通过点击链接浏览网站时,它都会更新历史记录。 - GROVER.
@ColdCerberus 没关系,这种事情经常发生 :) 好的,我会做的。 - GROVER.
如果这段JS代码被包含在你的所有页面中,那么window.Traveller = new Traveller();将在每次页面重定向时执行,重置window.TravellerupdateHistory()函数中的this._initialised值。不确定这是否是您期望的行为。 - Anis R.
@AnisR。它包含在初始加载中,当用户重定向时不会重新加载 :) - GROVER.
显示剩余10条评论
2个回答

5
你是否有一个名为 onpopstate 的处理程序?
如果有的话,那么请检查是否在此处进行推送历史记录。一些条目被删除/替换可能是一个很大的迹象。事实上,请参考这个SO答案
请记住,history.pushState()会将新状态设置为最新的历史记录状态,并且当您导航(向后/向前)到您设置的状态时将调用window.onpopstate。因此,在调用window.onpopstate时不要pushState,因为这将把新状态设置为最后状态,然后就没有可以前进的状态了。
我曾经遇到过你描述的完全相同的问题,但实际上是我回去和尝试理解错误,这最终将触发popState处理程序。从那个处理程序中,它会调用history.push。所以最后,我也有一些重复的条目,一些缺失的条目,没有任何逻辑的解释。
我删除了对history.push的调用,在检查一些条件后用history.replace替换它,最后就像魅力一样工作了 :)
编辑-->提示
如果您找不到哪个代码正在调用history.pushState:
请尝试使用以下代码覆盖history.pushState和replaceState函数:
window.pushStateOriginal = window.history.pushState.bind(window.history);
window.history.pushState = function () {
    var args = Array.prototype.slice.call(arguments, 0);
    let allowPush  = true;
    debugger;
    if (allowPush ) {
        window.pushStateOriginal(...args);
    }
}
//the same for replaceState
window.replaceStateOriginal = window.history.replaceState.bind(window.history);
window.history.replaceState = function () {
    var args = Array.prototype.slice.call(arguments, 0);
    let allowReplace  = true;
    debugger;
    if (allowReplace) {
        window.replaceStateOriginal(...args);
    }
}

当每次触发断点时,请查看调用栈。

在控制台中,如果您想阻止pushState,请在恢复之前输入allowPush = false;allowReplace = false;。 这样,您不会错过任何history.pushState,并且可以回溯并找到调用它的代码 :)


我执行了,但它不会推送或替换历史记录 :/ 它只是渲染页面。 - GROVER.
非常奇怪。尝试使用以下代码覆盖history.push函数: const hP = history.pushState history.pushState = (loc)=>{ debugger; hP(loc)} 并对history.replaceState执行相同的操作。 然后,每当断点被触发时,请查看调用堆栈。 - Bonjour123
我检查了调用栈,一切似乎都做得正确 - 但是历史记录无法工作。为什么Mozilla要让所有事情变得如此困难 :( - GROVER.
谢谢 :) 如果您在Chrome上尝试,您有什么结果? - Bonjour123

2

我在一个非常简单的应用程序上遇到了同样的问题,我只从代码中的一个地方调用window.history.pushState()。在FireFox中一切都很顺利,但出现了你提到的重复/覆盖问题。结果发现解决方法是更改:

document.title = "My New Title";
window.history.pushState(null, null, '/some/path');

window.history.pushState(null, null, '/some/path');
document.title = "My New Title";

现在它在两个浏览器中都像梦想一样工作。


太棒了,运作得很顺利,谢谢分享。 GA似乎可以追踪浏览器历史的所有内容,所以不需要发送GA事件...只需触碰历史记录,就可以正确追踪了。 - undefined

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