如何在 JavaScript 的 popstate 事件中执行 location.reload() 同时保留浏览器历史记录

5

我有这样一个场景,所有的URL都是使用history.pushState函数生成的。我没有使用任何服务器端技术。我只从服务器获取一次数据,然后使用Javascript生成URL。

现在我遇到的问题是使用popstate函数,它基本上模拟了浏览器的后退按钮行为。

仅限于点击一次“返回”按钮时,一切都运作正常。

    $(window).on('popstate', function (event) {
        location.reload(true);
        //window.location.href = document.location;
        //console.log("location: " + document.location);
    });

在点击一次后退按钮后,我无法看到浏览器的任何先前历史记录。我认为这是由于location.reload方法导致的。我尝试储存所有的位置,然后进行重定向,但是同样的事情发生了——浏览器历史记录在刷新后丢失。

有没有一种方法可以实现多次单击后退按钮时不会使浏览器丢失历史记录?

如果有JQuery选项的话,如果有人能分享他们对这个方面的知识就太好了。


1
你尝试过将所有位置存储到“localStorage”中,然后使用重定向进行操作吗? - user2560539
你需要一些持久性的东西来存储历史记录。可以使用 cookies、localStorage、sessionStorage(只能在页面关闭之前工作)或服务器端存储。 - Cody Pace
@PeterDarmis 如果我使用localStorage来跟踪最近访问的历史记录,那不是一项繁琐的任务吗?你有什么策略想要分享吗? - Rahul Sharma
@CodyPace 是正确的,但使用上述方法中的跟踪部分会非常困难。如果用户点击返回按钮3-4次,我该如何跟踪?我如何将这些事件记录在持久存储中? - Rahul Sharma
@RahulSharma 历史状态保存在history对象中,https://developer.mozilla.org/en-US/docs/Web/API/History/pushState。在每个访问页面上也将状态传递到localStorage。如果历史记录被清除,可以从localStorage恢复它。 - user2560539
此外,由于存在某些浏览器模式不会保留历史记录或可能阻止使用localStorage的情况,例如请参阅https://michalzalecki.com/why-using-localStorage-directly-is-a-bad-idea/#:~:text=Local%20storage%20works%20perfectly%20fine,until%20you%20quit%20the%20browser。在这种情况下,也许像本文所写的那样,在后端会话中保留历史快照是一个好主意。 - user2560539
2个回答

2

我写了一个简单的插件,基于历史记录API,我在我的项目中使用它。它可以实现您所要求的功能。

        // instantiate the QueryString Class.            
        qsObj=new QueryString({

            onPopstate: function(qsParams, data, dataAge, e) {
                // This function is called on any window.popstate event
                //   -- triggered when user click browser's back or forward button.
                //   -- triggered when JS manipulates history.back() or history.forward()
                //   -- NOT triggered when any of the QueryString methods are called (per the native behavior of the HTML5.history API which this class uses.)
                //   -- Might be triggered on page load in some browsers.  You can handle this by checking for the existence of data, eg:  if(data){ 'do something with the data' }else{ 'move along nothing to do here' }

                console.log('executing onPopstate function');
                console.log('page query string parameters are: ', qsParams);
                console.log('stored page data is: ', data);
                console.log('dataAge is:', dataAge, ' seconds old');

                // do stuff..

                if(data) {
                    if(dataAge && dataAge<=20) {
                        // use the stored data..

                    } else {
                        // it's old data get new..
                        
                    }
                } else {
                    // the current page has no stored data..
                }
            },

            onDocumentReady: function(qsParams) {
                // Document ready is only called when a page is first browsed to or the page is refreshed.
                // Navigating the history stack (clicking back/forward) does NOT refire document ready.
                // Use this function to handle any parameters given in the URL and sent as an object of key/value pairs as the first and only parameter to this function.

                console.log('executing onDocumentReady function');
                console.log('page query string parameters are:',qsParams);

                // do stuff..
            }

        });

插件使用:

// == QueryString class =============================================================================================
//
//    Modifies the query string portion of a browsers URL, updates the browsers history stack - saving page data 
//    along with it, all without reloading the page.
//    Can be used to simply obtain querystring parameter values as well.    
//
//    == Instantiate: ================================
//
//    var qsObj=new QueryString(
//
//        onPopstate: function(qsParams, data, dataAge, e) {
//
//            // This function is called on any window.popstate event
//            //   -- triggered when user click browser's back or forward button.
//            //   -- triggered when JS manipulates history.back() or history.forward()
//            //   -- NOT triggered when any of the QueryString methods are called (per the native behavior of the
//            //      HTML5.history API which this class uses.)
//            //   -- Might be triggered on page load in some browsers.  You can handle this by checking for the 
//            //      existence of data, eg:  
//            //            if(data){ 'do something with the data' }else{ 'move along nothing to do here' }
//
//            // -- qsParams: is an object that contains the current pages query string paramters as key:value pairs.
//            // -- data: is an object that contains any page data that was stored at the time that this page was added
//            //    to the history stack via this class (or any HTML5 history API method),  otherwise this value is NULL!
//            // -- dataAge:  null if data is null or the page data was not added via the methods in this class, 
//            //    otherwise the value is the age of the data in seconds.
//            // -- e: the event object if you want it.
//
//            if(data){
//                if(dataAge && dataAge <= 600){ // do it this way otherwise you'll error out if dataAge is null.
//                    // There is data and it is less than 10 minutes old.
//                    // do stuff with it..
//                }
//            }
//        },
//
//        onDocumentReady: function(qsParams){
//            // Document ready is only called when a page is first browsed to or the page is refreshed.
//            // Navigating the history stack (clicking back/forward) does NOT refire document ready.
//            // Use this function to handle any parameters given in the URL and sent as an object of key/value pairs as 
//            // the first and only parameter to this function.
//
//            // do stuff with any qsParams given in the URL..
//
//        }
//
//    });
//
//
//    == The following methods are available: =======================================
//
//
//    var qsParams = qsObj.parseQueryString(); // returns an object that contains the key/value pairs of all the query 
//                                             // string parameter/values contained in the current URL, or an empty object
//                                             // if there are none.
//
//
//    qsObj.update({
//
//        // Use this method to add/remove query string parameters from the URL and at the same time update, or add to, the 
//           browser history stack with the ability to also save page data in with the history.
//
//        opType: 'auto',
//        //  -- Optional. Allowed values: ['replace'|'push'|'auto'], Default is 'auto' unless 'push' or 'replace' is 
//        //     specifically given.
//        //  -- 'push':    Adds the new URL and any page data onto the browser history stack.
//        //  -- 'replace': Overwrites the current page history in the stack with the new URL and/or page data
//        //  -- 'auto':    compares the initial qs parameters with the updated qs parameters and if they are the same 
//        //      does a 'replace', if they are different does a 'push'.
//
//        qsParams: {
//            hello: 'world',
//            another: 'pair'
//        },
//        //  -- Optional. Object that contains key/value pairs to add to the query string portion of the URL.
//        //  -- Will entirely replace what is/isn't currently in the query string with the given key/value pairs.
//        //  -- The parameters contained in the url querystring will be made, or unmade, based on the key/value pairs 
//        //     included here so be sure to include all of the pairs that you want to show in the URL each time.
//
//
//        data: {
//           key1: 'value1',
//           key2: 'value2'
//        }
//        // Optional, Object that contains key/value pairs to store as page data in the browser history stack for this page.
//
//        // ** If qsParams and data are ommitted then nothing silently happens. (This is not the same as given but empty, 
//        //    in which case something happens.)
//
//    });
//
//
//    qsObj.Clear({
//
//       // Use this method to remove all query string parameters from the URL and at the same time update, or add to, the
//       // browser history stack with the ability to also save page data in with the history.
//
//       optype: 'auto' // optional, defaults to auto.
//
//       data: {} // Optional, defaults to empty object {}.
//    });
//
// =========================================================================================================================

插件代码:

; (function () {
var Def = function () { return constructor.apply(this, arguments); };
var attr = Def.prototype;

//== list attributes
attr.popstateCallback = null;
attr.docreadyCallback = null;
attr.skipParseOnInit = false;
attr.currentQS;

//== Construct
function constructor(settings) {
    if (typeof settings === 'object') {
        if ('onPopstate' in settings && typeof settings.onPopstate === 'function') {
            this.popstateCallback = settings.onPopstate;
        }
        if ('onDocumentReady' in settings && typeof settings.onDocumentReady === 'function') {
            this.docreadyCallback = settings.onDocumentReady;
        }
    }
    if (this.skipParseOnInit !== true) {
        this.currentQS = this.parseQueryString();
    }
    var self = this;
    if (typeof this.popstateCallback === 'function') {
        $(window).on('popstate', function (e) {
            var data = null;
            var dataAge = null;
            if (typeof e === 'object' && 'originalEvent' in e && typeof e.originalEvent === 'object' && 'state' in e.originalEvent && e.originalEvent.state && typeof e.originalEvent.state === 'object') {

                data = e.originalEvent.state;
                if ('_qst_' in data) {
                    dataAge = ((new Date).getTime() - e.originalEvent.state['_qst_']) / 1000; // determine how old the data is, in seconds
                    delete data['_qst_'];
                }
            }
            var qsparams = self.parseQueryString();
            self.popstateCallback(qsparams, data, dataAge, e);
        });
    }

    if (typeof this.docreadyCallback === 'function') {
        $(document).ready(function () {
            self.docreadyCallback(self.currentQS);
        });
    }
}

//== Define methods ============================================================================

attr.parseQueryString = function (url) {
    var pairs, t, i, l;
    var qs = '';
    if (url === undefined) {
        var loc = window.history.location || window.location;
        qs = loc.search.replace(/^\?/, '');
    } else {
        var p = url.split('?');
        if (p.length === 2) {
            qs = p[1];
        }
    }
    var r = {};
    if (qs === '') {
        return r;
    }
    // Split into key/value pairs
    pairs = qs.split("&");
    // Convert the array of strings into an object        
    for (i = 0, l = pairs.length; i < l; i++) {
        t = pairs[i].split('=');
        var x = decodeURI(t[1]);
        r[t[0]] = x;
    }
    return r;
};

//-- Get a querystring value from it's key name         
attr.getValueFromKey = function (key) {
    var qs = this.parseQueryString();
    if (key in qs) {
        return decodeURIComponent(qs[key].replace(/\+/g, " "));
    } else {
        return null;
    }
};

//-- if urlValue is given then qsParams are ignored.    
attr.update = function (params) {
    if (typeof params !== 'object') { return; }

    var urlValue = null;
    var data = {};
    if ('data' in params) {
        data = params.data;
        urlValue = '';
    }

    var opType = 'opType' in params ? params.opType : 'auto';

    if ('urlValue' in params && typeof params.urlValue === 'string') {
        urlValue = params.urlValue;
        if (opType === 'auto') {
            var loc = window.history.location || window.location;
            if (loc.protocol + '//' + loc.host + loc.pathname + loc.search === urlValue || loc.pathname + loc.search === urlValue) {
                opType = 'replace'; // same URL, replace
            } else {
                opType = 'push'; // different URL, push
            }
        }
    } else if ('qsParams' in params && typeof params.qsParams === 'object') {

        var pairs = [];
        for (var key in params.qsParams) {
            pairs.push(key + '=' + params.qsParams[key]);
        }
        urlValue = '?' + pairs.join('&', pairs);
        if (opType === 'auto') {
            if (this.compareQsObjects(params.qsParams, this.currentQS) === false) { // different                    
                this.currentQS = params.qsParams;
                opType = 'push';
            } else { // same                    
                opType = 'replace';
            }
        }
    }
    this.replaceOrPush(urlValue, data, opType);
};

//== Add querystring
//-- just an alias to update
attr.add = function (params) {
    return this.update(params);
};

//== Remove specified querystring parameters
//   -- Use clear() method to remove ALL query string parameters
attr.remove = function (params) {
    var urlValue = null;
    var qsChanged = false;
    if ('qsParams' in params && params.qsParams.length > 0) {
        var qs = this.parseQueryString();
        var key;
        for (var i = 0, l = params.qsParams.length; i < l; ++i) {
            key = params.qsParams[i];
            if (key in qs) {
                delete qs[key];
                qsChanged = true;
            }
        }
    }
    if (qsChanged === true) {
        var pairs = [];
        for (key in qs) {
            pairs.push(key + '=' + qs[key]);
        }
        urlValue = '?' + pairs.join('&', pairs);
        var data = 'data' in params ? params.data : {};
        var opType = 'opType' in params ? params.opType : '';
        this.replaceOrPush(urlValue, data, opType);
    }
    return;
};

//== Delete querystring
//-- just an alias to remove
attr.delete = function (params) {
    return this.remove(params);
};

//== Removes all query string parameters.
//   Use remove() method to remove just the given parameters 
attr.clear = function (params) {
    params = typeof params === 'undefined' ? {} : params;
    var urlValue = window.history.location || window.location;
    urlValue = urlValue.protocol + '//' + urlValue.host + urlValue.pathname;
    var data = 'data' in params ? params.data : {};
    var opType = 'opType' in params ? params.opType : '';
    this.replaceOrPush(urlValue, data, opType);
    return;
};

//== Simple wrapper to the HTML5 history API's replaceState() method.
//   --also used internally
attr.replaceState = function (urlValue, data) {
    if (typeof urlValue !== 'string') {
        return;
    }
    if (typeof data !== 'object') {
        data = {};
    }
    data['_qst_'] = (new Date).getTime(); // store a timestamp value        
    history.replaceState(data, '', urlValue);
};

//== Simple wrapper to the HTML5 history API's pushState() method.
//   --also used internally
attr.pushState = function (urlValue, data) {
    if (typeof urlValue !== 'string') {
        return;
    }
    if (typeof data !== 'object') {
        data = {};
    }
    data['_qst_'] = (new Date).getTime(); // store a timestamp value        
    history.pushState(data, '', urlValue);
};

//-- internal use - simple gatekeeper to decide if there is anything to do and will default to 'replace' opType if this value is not given.
attr.replaceOrPush = function (urlValue, data, opType) {
    // is there anything to do?
    if (typeof urlValue === 'string' || typeof data === 'object') {
        // yes, what type of operation are we going to do?
        if (opType === 'push') {
            this.pushState(urlValue, data);
        } else {
            this.replaceState(urlValue, data);
        }
    }
    return;
};

// == internal use - compares the existing qs with a potentially updated one to see if they are the same (returns true) or not (returns false)
attr.compareQsObjects = function (a, b) {
    if (typeof a === 'object' && typeof b === 'object') {
        var aa = [];
        var bb = [];
        for (k in a) {
            aa.push(k + a[k]);
        }
        aa.sort();
        for (k in b) {
            bb.push(k + b[k]);
        }
        bb.sort();
        if (aa.join('') !== bb.join('')) { return false; }
        return true;
    }
    return null;
};

//unleash your class
window.QueryString = Def;

})();


谢谢你的回答。我会尝试一下,如果在我的情况下有效,我会告诉你的。 - Rahul Sharma
你能给我一个这个插件的使用示例吗?谢谢。 - Rahul Sharma
我创建了一个演示,https://jsfiddle.net/2c513gbo/,展示了它的用法。然而,iframe fiddle用于呈现HTML页面,阻止了演示按设计工作。如果您将HTML和JavaScript复制到自己的页面并运行,则演示将正常工作。 - Drew
我还更新了我的帖子,展示了如何以最基本的方式使用插件。 - Drew
我无法让它在我的端口运行(也许我没有理解它的要点),但我仍然会奖励您所做的努力,并且我相信您的插件可以很好地适用于我的情况。 - Rahul Sharma

0

嗯,这实际上是不可能的。 我唯一想到的诀窍是在popstate事件被触发时更改window.location.href,并将您的历史记录作为URL参数传递。

$(window).on('popstate', function (event) {
    window.location.href = 'https://here.com?history = ' + yourHistory;
});

你需要想办法在此方法中发送并处理你的历史记录,以便在用户再次点击“返回”按钮时能够重新发送。 你还需要确保生成的URL不会太长。


好的,我有一些人提出了建议,所以我会尝试它们,看看是否真的不可能。 - Rahul Sharma

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