Skip to main content
 首页 » 编程设计

jQuery ADS-ajax详解-检测hash变化解决ajax后退按钮和书签问题

2022年07月16日25jpfss
// 后退按钮和书签功能 
/* 
通过一些DOM脚本并利用URL的hash值可以补救后退按钮和标签的问题。 
其中,hash值表示浏览器应该保持在当前页面上,但同时必须通过重新定位页面使得与hash匹配的命名的锚(或带有相同ID属性的元素),在浏览器的视口(view port)中可见。如果文档中不存在与hash匹配的项,则浏览器只会改变地址栏中的URL。这里的hash可以连同URL一起被作为标签使用,而且通过一些DOM脚本及hack技术,hash还可以在用户单击后退按钮时用于更新页面。 
 */ 
/** 
 * 1.不那么简单的修复 
 * 修复后退按钮和变迁涉及监视和识别URL中hash值的变化,并通过该变化来调用Ajax请求。要做到这一点,需要创建一个检测地址中hash值变化的对象,同时以预先定义的适当的方法进行响应。这个对象需要做以下几件事: 
 *      (1)是永不唐突的DOM脚本增强文档以跟踪页面的变化。 
 *      (2)允许注册根据不同hash值作为反映的不同方法。 
 *      (3)监事地址栏的变化并调用注册过的适当方法。 
 *      (4)在处理后退按钮和标签时,适应各种不同产品化浏览器的古怪行为。 
 * 每种浏览器处理地址的方式都有点不一样: 
 *      (1)在IE中,后退和前进按钮会忽略hash值,而只基于URL的其余部分进行导航。为了解决这个问题,需要使用一个隐藏的<iframe>并通过向GET字符串中添加hash来模拟导航到不同的网页。 
 *      (2)在Safari中,当通过带hash的URL向前或向后导航时,浏览器的history及history.length会相应改变。但是,window.location.href的值会保持为通过后退和前进按钮导航之前,浏览器中打开的最后一个地址。考虑到安全原因,我们无法访问history中的URL,因此需要相对于Safari的history的长度保持跟踪访问过的hash值,并且能够从存储列表中找回适当的hash。 
 *      (3)Firefox和Opera的行为则都更符合常理,他们会在使用后退和前进按钮通过hash来导航时,同步更新window对象的location值。 
 * 
 * 2.针对产品功能的浏览器嗅探 
 * 在处理具体浏览器的产品功能时,浏览器嗅探是一种可以接受的方案,而且也是唯一可能的方案,因为产品的差异并不针对Javascript中的能力或对象。 
 * 
 * 3.跟踪地质变化 
 */ 
 
/* 一个用来基于hash触发注册的方法的URL hash侦听器 */ 
    var actionPager = { 
        // 前一个hash 
        lastHash: '', 
        // 为hash模式注册的方法列表 
        callbacks: [], 
        // Safari历史记录列表 
        safariHistory: false, 
        // 对为IE准备的iframe的引用 
        msieHistory: false, 
        // 应该被转换的链接的类名 
        ajaxifyClassName: '', 
        // 应用程序的根目录。当创建hash时 
        // 它将是被清理后的URL 
        ajaxifyRoot: '', 
 
        /** 
         * 初始化页面(pager)功能 
         * @param {String} ajaxifyClass 预定义的类名,只有符合该类名的链接才具备功能,默认为 ADSActionLink 
         * @param {String} ajaxifyRoot 预定义的根路径 
         * @param {String} startingHash 预定义的hash值,默认为start 
         * @example actionPager.init(); 
         */ 
        init: function (ajaxifyClass, ajaxifyRoot, startingHash) { 
 
            this.ajaxifyClassName = ajaxifyClass || 'ADSActionLink'; 
            this.ajaxifyRoot = ajaxifyRoot || ''; 
 
            var ua = navigator.userAgent; 
            if (/Safari/i.test(ua) && /Version\/3/.test(ua)) { 
                this.safariHistory = []; 
            } else if (/MSIE/i.test(ua) && (parseInt(ua.split('MSIE')[1]) < 8)) { 
                // 如果是MSIE 6/7,添加一个iframe以便 
                // 跟踪重写(override)后退按钮 
                this.msieHistory = document.createElement('iframe'); 
                this.msieHistory.setAttribute('id', 'msieHistory'); 
                this.msieHistory.setAttribute('name', 'msieHistory'); 
                this.msieHistory.setAttribute('src', 'fakepage'); 
                setStyleById(this.msieHistory, { 
                    'width': '100px', 
                    'height': '100px', 
                    'border': '1px solid black', 
                    'display': 'none', 
                    'z-index': -1 
                }); 
                document.body.appendChild(this.msieHistory); 
                this.msieHistory = frames.msieHistory; 
            } 
 
            // 将链接转换为Ajax链接 
            this.ajaxifyLinks(); 
 
            // 取得当前的地址 
            var locat = this.getLocation(); 
 
            // 检测地址中是否包含hash(来自书签) 
            // 或者是否已经提供了hash 
            if (!locat.hash && !startingHash) { 
                startingHash = 'start'; 
            } 
 
            // 按照需要保存hash 
            var ajaxHash = this.getHashFromURL(locat.hash) || startingHash; 
            this.addBackButtonHash(ajaxHash); 
 
            // 添加监视事件以观察地址栏中的变化 
            var watcherCallback = makeCallback(this.watchLocationForChange, this); 
 
            if (/MSIE/i.test(ua) && (parseInt(ua.split('MSIE')[1]) < 8)) { 
                window.setInterval(watcherCallback, 200); 
            } else { 
                addEvent(window,'hashchange',watcherCallback); 
            } 
 
        }, 
 
        /** 
         * 会自动将任何带标识的锚标签转换为页面(pager)可以识别的链接 
         */ 
        ajaxifyLinks: function () { 
            // 将链接转换为锚以便Ajax进行处理 
            var links = getElementsByClassName(this.ajaxifyClassName, document, 'a'); 
            for (var i = 0, len = links.length; i < len; i++) { 
                var curLink = links[i]; 
                if (hasClassName(curLink, 'ADSActionPagerModified')) { 
                    continue; 
                } 
 
                // 将href属性转换为#value形式 
                curLink.setAttribute('href', this.convertURLToHash(curLink.getAttribute('href'))); 
                addClassName(curLink, 'ADSActionPagerModified'); 
 
                // 注册单击事件以便在必要时添加历史纪录 
                addEvent(curLink, 'click', function () { 
                    if (this.href && this.href.indexOf('#') > -1) { 
                        actionPager.addBackButtonHash(actionPager.getHashFromURL(this.href)); 
                    } 
                }); 
            } 
        }, 
 
        /** 
         * 记录后退行为 
         * @param {String} ajaxHash hash值 
         * @return {Boolean} 
         */ 
        addBackButtonHash: function (ajaxHash) { 
            // 保存hash 
            if (!ajaxHash) { 
                return false; 
            } 
            if (this.safariHistory) { 
                // 为Safari使用特殊数组 
                if (this.safariHistory.length === 0) { 
                    this.safariHistory[window.history.length] = ajaxHash; 
                } else { 
                    this.safariHistory[window.history.length + 1] = ajaxHash; 
                } 
                return true; 
            } else if (this.msieHistory) { 
                // 在MSIE中通过导航iframe 
                this.msieHistory.document.execCommand('Stop'); 
                this.msieHistory.location.href = 'fakepage?hash=' + 
                    ajaxHash + 
                    '&title=' + 
                    document.title; 
                return true; 
            } else { 
                // 通过改变地址的值 
                // 使用makeCallback包装函数 
                // 以便在超时方法内部使this 
                // 引用actionPager 
                var timeoutCallback = makeCallback(function () { 
                    if (this.getHashFromURL(window.location.href + window.location.hash) !== ajaxHash) { 
                        location.replace(location.href + '#' + ajaxHash); 
                    } 
                }, this); 
                setTimeout(timeoutCallback, 200); 
                return true; 
            } 
            return false; 
        }, 
 
        /** 
         * 间隔地检测hash的变化,还可执行注册的侦听器 
         */ 
        watchLocationForChange: function () { 
            var newHash; 
            // 取得新的hash值 
            if (this.safariHistory) { 
                // 在Safari中从历史记录数组中取得 
                if (this.safariHistory[history.length]) { 
                    newHash = this.safariHistory[history.length]; 
                } 
            } else if (this.msieHistory) { 
                // 在MSIE中从iframe的地址中取得 
                newHash = this.msieHistory.location.href.split('&')[0].split('=')[1]; 
            } else if (location.hash !== '') { 
                // 对其他浏览器从window.location中取得 
                newHash = this.getHashFromURL(window.location.href); 
            } 
 
            // 如果新hash与最后一次的hash不相同,则更新页面 
            if (newHash && this.lastHash !== newHash) { 
                if (this.msieHistory && this.getHashFromURL(window.location.href) !== newHash) { 
                    // 修复MSIE中的地址栏 
                    // 以便能适当地加上标签(或加入收藏夹) 
                    location.hash = newHash; 
                } 
 
                // 在发生异常的情况下使用try/catch 
                // 结构尝试执行任何注册的侦听器 
                try { 
                    this.executeListeners(newHash); 
                    // 在通过处理程序添加任何 
                    // 新链接的情况下进行更新 
                    this.ajaxifyLinks(); 
                } catch (ex) { 
                    // 这里将捕获到回调函数中的任何异常JS 
                    alert(ex); 
                } 
 
                // 将其保存为最后一个hash 
                this.lastHash = newHash; 
            } 
        }, 
 
        /** 
         * 用于根据特殊的hash来注册侦听器 
         * @param {RegExp || String} regex 正则表达式 
         * @param {Function} method 执行的方法 
         * @param {Object || Element} context 执行环境,上下文 
         */ 
        register: function (regex, method, context) { 
            var obj = {'regex': regex}; 
            context = context || window; 
            // 一个已经指定的环境 
            obj.callback = function (matches) { 
                method.apply(context, matches); 
            }; 
 
            // 将侦听器添加到回调函数数则中 
            this.callbacks.push(obj); 
        }, 
 
        /** 
         * 把链接的URL地址转换为hash值 
         * @param {String} url 
         * @return {*} 
         */ 
        convertURLToHash: function (url) { 
            if (!url) { 
                // 没有url,因而返回一个'#' 
                return '#'; 
            } else if (url.indexOf('#') > -1) { 
                // 存在hash,因而返回它 
                return url.split('#')[1]; 
            } else { 
                // ie67会自动添加域名 
                // 如果URL中包含域名(MSIE)则去掉它 
                if (url.indexOf('://') > -1) { 
                    var locatH = window.location.href; 
                    locatH = locatH.substring(0, locatH.lastIndexOf('/')); 
                    var s = ''; 
                    var len = Math.min(locatH.length, url.length); 
                    for (var i = 0; i < len; i++) { 
                        if (locatH.charAt(i) !== url.charAt(i)) { 
                            break; 
                        } else { 
                            s += url.charAt(i); 
                        } 
                    } 
 
                    var reg = new RegExp(s + '/'); 
                    url = url.replace(reg, ''); 
                    //url = url.match(/:\/\/[^\/]+(.*)/)[1]; 
                } 
                // 按照init()约定去掉根目录 
                return '#' + url.substring(this.ajaxifyRoot.length); 
            } 
        }, 
 
        /** 
         * 从指定的URL中提取出hash值 
         * @param url 
         * @return {*} 
         */ 
        getHashFromURL: function (url) { 
            if (!url || url.indexOf('#') === -1) { 
                return ''; 
            } 
            return url.split('#')[1]; 
        }, 
 
        /** 
         * 获取当前URL地址 
         * @return {*} 
         */ 
        getLocation: function () { 
            // 检查hash 
            if (!window.location.hash) { 
                // 没有则生成一个 
                var url = { 
                    domain: null, 
                    hash: null 
                }; 
                if (window.location.href.indexOf('#') > -1) { 
                    var parts = window.location.href.split('#')[1]; 
                    url.domain = parts[0]; 
                    url.hash = parts[1]; 
                } else { 
                    url.domain = window.location; 
                } 
                return url; 
            } 
            return window.location; 
        }, 
 
        /** 
         * 执行侦听器 
         * @param hash 
         */ 
        executeListeners: function (hash) { 
            var matches; 
            // 执行与hash匹配的任何侦听器 
            if (this.callbacks.length) { 
                for (var i in this.callbacks) { 
                    if ((matches = hash.match(this.callbacks[i].regex))) { 
                        this.callbacks[i].callback(matches); 
                    } 
                } 
            } 
 
        } 
    }; 
 
    window.ADS.actionPager = actionPager;

本文参考链接:https://www.cnblogs.com/webFrontDev/archive/2013/02/01/2888581.html