javascript - PushState 创建重复的历史条目并覆盖以前的历史条目

标签 javascript

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

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

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

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

  2. 导航到 profile.php(历史记录将是:个人资料、索引)

  3. 导航到 search.php(历史记录将是:搜索、搜索、索引)

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

感谢所有帮助,
干杯。

最佳答案

您有 onpopstate 处理程序吗?

如果是,那么也请检查一下您是否不推送历史记录。历史列表中的某些条目被删除/替换可能是一个重要的迹象。确实,请参阅此SO answer :

Keep in mind that history.pushState() sets a new state as the newest history state. And window.onpopstate is called when navigating (backward/forward) between states that you have set.

So do not pushState when the window.onpopstate is called, as this will set the new state as the last state and then there is nothing to go forward to.

我曾经遇到过与您描述的完全相同的问题,但这实际上是由于我反复尝试理解该错误而导致的,这最终会触发 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,并且可以向上查找调用它的代码:)

关于javascript - PushState 创建重复的历史条目并覆盖以前的历史条目,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58627234/

相关文章:

javascript - 使用 fadeIn 滚动 div

javascript - 为动态 OO 程序实现现有的脚本语言

javascript - 注销计时器 php/javascript

javascript - 在 php 函数期间生成反馈。 (通过 AJAX?)

javascript - 使用 javascript 继续或进入 PHP 循环

javascript - 在命名空间中混合 javascript 和 jquery

javascript - 为什么在 windows.onload 事件处理程序中创建的范例不是全局可见的?

javascript - 更改拉斐尔 "Label"的背景颜色

javascript - 在 Javascript 中绘制形状

javascript - fetch 返回 SyntaxError : Unexpected token T in JSON at position 0