XHR与浏览器导航冲突解决方案

本文探讨了使用iframe技术来实现浏览器的前进后退导航功能及书签功能,适用于不需要修改或删除操作的界面切换场景。

研究 gmail 有感

 

理论:

 

历史:

最早没有 xhr 的时候,要想得到无刷新的用户体验,唯一的方法就是隐藏帧技术(frame) ,发展到现在的 iframe,这个技术仍然有它的使用场景,即在文件上传以及 简单情况下浏览器的前进后退导航功能 上。

 

使用:

当一个页面嵌入一个iframe时,当javascript变动iframe的src(或者操作document open 与 close),会在浏览器上生成一条历史记录,点击后退的话,主页面没有变化,但是iframe会加载历史记录中的上一条src(或者文档),因此可以解决传统用户体验的浏览器导航部分。

 

适用范围:

一般用于界面切换,当界面有大的变化,且没有修改或删除操作时 ,可以由用户点击浏览器的返回操作来达到上一个操作界面。

 

技术描述:(firefox 适用,ie不适用)


主页面 javascript 设置里面的 iframe 的 src (向服务器传递信息),当iframe 加载完毕后,在iframe的页面调用主页面的回调函数,达到更新主页面的作用,并向浏览器添加一条历史记录。当用户点击后退后,iframe会加载历史记录的上一条页面,重新调用上一个页面所应该的主页面callback,修改主页面到上一个功能界面,达到是主页面回退的效果. iframe 一般是设置样式 display:none,排除在文档流中,不参与用户界面。

 

示例 (firefox 适用):

 

主页面 :index.html

 

<iframe id='i' style='display:none'></iframe>
<div id='d'></div>
<div id='d2'></div>
<input type='button' onclick='bclick();' value='change'/>
<script>
	var ii=0;
	function callback(str){
		//alert(ii);
		document.getElementById('d').innerHTML='iframe call back :'+str;
	}
	
	function bclick() {
		document.getElementById('d2').innerHTML='mimic ajax '+ii;
		document.getElementById('i').src='iframe.html?i='+(ii++);
	}
	
	window.onload=function(){
		document.getElementById('i').src='iframe.html';
	}
	</script>
 

 

隐藏帧页面 :iframe.html

 

 

<script>
	window.onload=function(){
		parent.callback(window.location);
		
	}	
</script>
 

 

实战(多浏览器解决方案)

 

根据 advanced dom scripting 以及 YUI YAHOO.util.History 提出的方案,可以解决浏览器导航问题以及书签功能。



对于 firefox,ie8 可以直接给url添加 hash ,则firefox,ie8会把变动的 url加到浏览器history中,而ie6,7 则不会。


 firefox 


   每次操作添加 hash 到 url,并后台 setInterval 监控 url hash 的变化,根据当前的hash 进行 ajax 操作,( 用户点击后退只会改变hash值。

 

updated 2010-12-29 :

firefox chrome 已经支持 hashchange 事件,不再需要定时器监控了,监听事件即可。



 ie 6,7


   设置隐藏的iframe,每次操作用脚本设置iframe的内容 (注意:如果仅仅设置 src 参数 ?x=xx 不同,ie6,7 并不会产生历史纪录,必须src不同,直接脚本产生 iframe 新document(open,write,close)也可以产生历史纪录),将当前hash 插入到生成的iframe内容中,后台监控 setInterval  iframe 内容中 hash 的变化,如果变化( 用户点击后退会使当前页面iframe的内容变化 ),则提取hash值,添加到主页面url,执行相关ajax 操作。

 

YUI BHM 相关操作:

 

/**
     * Update the IFrame with our new state.
     *
     * @method _updateIFrame
     * @private
     * @return {boolean} true if successful. false otherwise.
     */
    function _updateIFrame (fqstate) {

        var html, doc;

        html = '<html><body><div id="state">' + fqstate + '</div></body></html>';

        try {
            doc = _histFrame.contentWindow.document;
            doc.open();
            doc.write(html);
            doc.close();
            return true;
        } catch (e) {
            return false;
        }
    }

 

关键是 open() ,close() 会增加历史 ,之后可以将数据附在 doc 上,后退的话数据就会回来,但是注意 close() 后会触发 iframe load 事件,yui3 采用 hash 来存储数据:

 

 

HistoryHash._updateIframe = function (hash, replace) {
        var iframeDoc      = iframe.contentWindow.document,
            iframeLocation = iframeDoc.location;


        iframeDoc.open().close();

        if (replace) {
            iframeLocation.replace(hash.charAt(0) === '#' ? hash : '#' + hash);
        } else {
            iframeLocation.hash = hash;
        }
    };
 

 

ie8


很简单,由于有相关事件直接监控 Hash 变动即可 ,不用像firefox一样轮循查看 :

 

if (YAHOO.env.ua.ie) {

            if (typeof document.documentMode === "undefined" || document.documentMode < 8) {

                // IE < 8 or IE8 in quirks mode or IE7 standards mode
                //利用 iframe 来生成历史纪录
                _checkIframeLoaded();

            } else {

                // IE8 in IE8 standards mode
                //直接利用特有事件
                YAHOO.util.Event.on(top, "hashchange",
                    function () {
                        var hash = _getHash();
                        _handleFQStateChange(hash);
                        _storeStates();
                    });

                _initialized = true;
                YAHOO.util.History.onLoadEvent.fire();

            }

        } else {
            //其他浏览器,轮训hash值变化
            // Start the thread that will have the responsibility to
            // periodically check whether a navigate operation has been
            // requested on the main window. This will happen when
            // YAHOO.util.History.navigate has been called or after
            // the user has hit the back/forward button.

            // On Safari 1.x and 2.0, the only way to catch a back/forward
            // operation is to watch history.length... We basically exploit
            // what I consider to be a bug (history.length is not supposed
            // to change when going back/forward in the history...) This is
            // why, in the following thread, we first compare the hash,
            // because the hash thing will be fixed in the next major
            // version of Safari. So even if they fix the history.length
            // bug, all this will still work!
            counter = history.length;

            // On Gecko and Opera, we just need to watch the hash...
            hash = _getHash();

            setInterval(function () {

                var state, newHash, newCounter;

                newHash = _getHash();
                newCounter = history.length;
                if (newHash !== hash) {
                    hash = newHash;
                    counter = newCounter;
                    _handleFQStateChange(hash);
                    _storeStates();
                } else if (newCounter !== counter && YAHOO.env.ua.webkit) {
                    hash = newHash;
                    counter = newCounter;
                    state = _fqstates[counter - 1];
                    _handleFQStateChange(state);
                    _storeStates();
                }

            }, 50);
 

书签问题:


    多个浏览器都可以通过,添加书签附带了当前页面的hash值。载入书签时,首先读取Hash值进入相应的ajax操作。

demo:

 

updated : 2010-12-29

 

由于 ie>7 以及 firefox ,chrome 都已经支持 hashchange 事件以及历史存储,那么实际上难点就是 ie6,7 下使用 iframe 来记录导航历史 :

 

simple hashchange for ie6,7 @ google code

 

kissy 采用先进的事件补丁技术优雅地解决了这个问题 ,在任何浏览器下都可以直接

 

function t() {
        alert("current hash :" + window.location.hash);
    }
    KISSY.Event.on(window, "hashchange", t);

 

即可 !

 

PS:html5 提出了新的标准 ,目前 chrome 下已经实现:

 

session history and navigation

 

github 也做了尝试:

 

how to rewrite url without refresh like github

 

PS2: 如果不想使得修改 hash 而引起浏览器增加历史记录,可使用 location.replace('#xx') ,对于 ie6,7 模拟的iframe,情况则只需要只修改 body 的 innerHtml :

 

iframe.contentWindow.document.body.innerHTML='#xx'
 

 

 

 

 

 

<think>我们正在讨论Ajax解决浏览器缓存问题。浏览器缓存有时会导致Ajax请求返回旧数据,而不是从服务器获取最新数据。为了解决这个问题,有几种常见方法。 常见解决方案: 1. 在URL后添加时间戳或随机数,使每次请求的URL不同,从而避免缓存。 例如:url = "data.php?timestamp=" + new Date().getTime(); 或者:url = "data.php?rand=" + Math.random(); 2. 设置HTTP请求头,禁用缓存。 使用XMLHttpRequest时,可以设置请求头: xhr.setRequestHeader("Cache-Control", "no-cache, no-store, must-revalidate"); xhr.setRequestHeader("Pragma", "no-cache"); xhr.setRequestHeader("Expires", "0"); 3. 在jQuery中,可以使用cache: false选项,它会在URL后添加时间戳。 $.ajax({ url: "data.php", cache: false, // 其他配置... }); 4. 使用POST请求代替GET请求,因为浏览器通常不会缓存POST请求。 5. 在服务端设置响应头,禁止缓存。 例如,在PHP中: header("Cache-Control: no-cache, must-revalidate"); header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // 一个过去的日期 注意:不同的浏览器缓存行为可能不同,但以上方法通常有效。 下面是一个使用时间戳的示例代码: ```javascript function fetchData() { var xhr = new XMLHttpRequest(); var url = "api/data"; // 添加时间戳避免缓存 url += (url.indexOf('?') === -1 ? '?' : '&') + "t=" + new Date().getTime(); xhr.open('GET', url, true); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log(JSON.parse(xhr.responseText)); } }; xhr.send(); } ``` 在Fetch API中,可以通过在请求中添加cache控制选项(注意:Fetch API的cache选项HTTP缓存相关,可以设置为'no-cache'等): ```javascript fetch('api/data', { cache: 'no-cache', // 告诉浏览器不检查缓存,直接向服务器请求(但可能会使用缓存验证) headers: { 'Cache-Control': 'no-cache' } }) .then(response => response.json()) .then(data => console.log(data)); ``` 注意:Fetch API的cache选项有多个值,如'default'、'no-store'、'reload'、'no-cache'、'force-cache'、'only-if-cached'。其中,'no-cache'表示不使用缓存验证,而是直接向服务器请求(但可能会使用缓存验证,如果服务器返回304则使用缓存)。要完全忽略缓存,可以使用'reload'。 另外,使用版本号也是一种常见做法,特别是在资源文件上。但对于Ajax请求,通常使用时间戳或随机数更简单。 最后,需要根据具体情况选择合适的方法。如果数据更新频繁,建议使用时间戳或设置请求头;如果使用框架(如jQuery),则可以直接设置cache:false。 现在,根据规则,我们需要在最后提出3-5个相关问题。</think>### Ajax解决浏览器缓存问题详解 #### 常见缓存问题及解决方案 1. **URL添加时间戳/随机数** 最常用的解决方案,确保每次请求URL唯一 ```javascript // 时间戳方案 const url = `api/data?_=${new Date().getTime()}`; // 随机数方案 const url = `api/data?r=${Math.random()}`; ``` 2. **设置请求头禁用缓存** 通过HTTP头显式控制缓存行为 ```javascript fetch(url, { headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' } }); ``` 3. **POST替代GET** POST请求默认不被浏览器缓存 ```javascript // 使用POST请求获取数据(即使不需要提交数据) fetch(url, { method: 'POST', body: JSON.stringify({}), headers: {'Content-Type': 'application/json'} }); ``` 4. **框架内置解决方案** jQuery等库提供内置缓存控制 ```javascript // jQuery示例 $.ajax({ url: "api/data", cache: false, // 自动添加时间戳参数 success: function(data) { /*...*/ } }); ``` #### 高级缓存控制策略 1. **ETag/Last-Modified验证** 服务端配合实现高效缓存验证 ```javascript // 服务端响应头示例 HTTP/1.1 200 OK ETag: "686897696a7c876b7e" Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT ``` 2. **Cache API管理** 使用Service Worker精确控制缓存 ```javascript // Service Worker中拦截请求 self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(cached => cached || fetch(event.request)) ); }); ``` 3. **版本化API端点** 通过API版本避免缓存冲突 ```javascript // 在URL中包含API版本 const apiUrl = `https://api.example.com/v2/data`; ``` #### 不同场景解决方案推荐 | **场景** | **推荐方案** | **注意事项** | |------------------------|---------------------------------------|----------------------------------| | 实时数据获取 | URL时间戳 + `Cache-Control: no-cache` | 避免过度请求 | | 大型静态资源 | ETag验证 + 长期缓存 | 设置合适max-age | | SPA应用数据 | API版本控制 + 客户端缓存管理 | 配合localStorage/IndexedDB | | 第三方API调用 | 随机数参数 + 请求头覆盖 | 注意API调用限制 | | 需要离线访问的应用 | Service Worker + Cache API | 实现缓存更新策略 | #### 最佳实践示例 ```javascript // 综合解决方案 async function fetchFreshData(endpoint) { const timestamp = new Date().getTime(); const url = `${endpoint}?_=${timestamp}`; try { const response = await fetch(url, { method: 'GET', headers: { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' } }); // 验证响应是否新鲜 if (response.headers.get('ETag')) { const cachedETag = localStorage.getItem(endpoint+'-etag'); if (cachedETag === response.headers.get('ETag')) { return JSON.parse(localStorage.getItem(endpoint)); } } const data = await response.json(); // 存储最新数据 localStorage.setItem(endpoint, JSON.stringify(data)); localStorage.setItem(endpoint+'-etag', response.headers.get('ETag')); return data; } catch (error) { console.error('请求失败,使用缓存数据', error); return JSON.parse(localStorage.getItem(endpoint) || '{}'); } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值