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


15

我创建了一个Web应用程序,该应用程序使用history pushStatereplaceState方法来浏览页面,同时更新历史记录。

该脚本本身几乎可以完美运行;它将正确加载页面,并在需要抛出页面时抛出页面错误。但是,我注意到一个奇怪的问题,该问题pushState会将多个重复的条目(并替换之前的条目)推送到历史记录中。

例如,假设我(按顺序)执行以下操作:

  1. 加载index.php(历史记录为:索引)

  2. 导航到profile.php(历史记录将是:Profile,Index)

  3. 导航到search.php(历史记录为:搜索,搜索,索引)

  4. 导航至dashboard.php

最后,这是我历史上将要发生的事情(按照最新到最旧的顺序):

仪表

仪表板仪表板
搜索
索引

这样做的问题是,当用户单击前进或后退按钮时,他们将被重定向到错误的页面,或者必须单击多次才能返回一次。那,如果他们去检查他们的历史记录就没有意义了。

这是我到目前为止的内容:

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);
        }
    }
});

感谢所有的帮助,
干杯。


因此,如果目标与最后一个条目不同,则只想传播历史更改?
阿鲁安·哈达德

这似乎是随机发生的吗?还是特定页面总是导致重复历史记录条目的页面。
SamVK '19

@AluanHaddad否我只想传播历史更改(如果存在历史更改)(即,当用户在站点中导航时)。类似于在正常情况下会发生的事情……(从索引到概要文件,再在StackOverflow上进行搜索将完全返回我的历史记录)
GROVER。

@SamVK没关系,从初始加载页面(即index.php)浏览后,条目将重复它们自己。
GROVER。

1
好吧,我不太确定,所以写评论。我看到您在功能中添加了浏览器历史记录updateHistory。现在,updateHistory在初始化Traveler(window.Traveller = new Traveller();constructor-> init-> send-> render->-> updateHistory)时,可能会被调用两次,即第一次,也可能是redirectclickeventListener中调用。我还没有测试过它,只是进行了疯狂的猜测,因此将其添加为注释而不是答案。
Akshit Arora

Answers:


3

你有一个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,19年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.