本文的所有代码在Firefox 11 beta + Scriptish 0.1.7环境作为用户脚本执行,某些行为可能会与普通的页面脚本不一致。

问题引出

在写CC98 Reply Improved脚本的时候,遇到一个场景是在帖子回复成功后,页面自动刷新,并且当引用或者编辑回复时,希望可以返回到当前被引用或者编辑的帖子楼层。在98中每个楼层都有一个锚链接(anchor), 这些锚链接的名称为数字,从0~9,其中1表示第一楼,2表示第二楼……0表示第十楼。假设当前的帖子链接为:

/dispbbs.asp?boardID=39&ID=3841624&star=20&page=1

则要跳转到第5楼,则需要在浏览器输入地址

dispbbs.asp?boardID=39&ID=3841624&star=20&page=1#5

其中#5这部分称为hash(我不大清楚中文应该叫什么)。

我们知道当在浏览器地址栏中输入与当前页面地址不同的目标地址时,浏览器会为我们载入目标页面;当目标地址与页面地址相同时,则页面不会刷新。即使上文所介绍的hash部分的内容不一样,页面同样不会刷新,而只是会在页面内部跳转到该hash值所指定的锚链接位置。假设帖子地址为:

dispbbs.asp?boardID=39&ID=3841624&star=20&page=1#5

若将末尾的#5改成#6,页面会跳到第6楼帖子而不会刷新,一般情况下这是可以接受的默认行为。

问题解决

回到最初的场景需要,在帖子回复之后需要刷新并跳转到相应的楼层,会遇到以下几种情况:

  1. 目标地址与当前地址不同。
  2. 目标地址与当前地址完全相同。
  3. 目标地址与当前地址仅hash部分不相同。

其中,第一种情况和第二种情况的解决方法比较简单,分别使用location.hreflocation.reload()即可解决:

location.href = targetURL;  // 第一种:将目标地址赋值给location.href
location.reload();   // 第二种:直接刷新本页面

然后,第三种情况相对比较麻烦,若以第一种的方法考虑,页面并不会刷新。因此需要寻找另外一种方法:在将页面地址改变后再打开新的页面地址。我想到的方法是两种,分别为:

// 第一种方法,使用setTimeout推迟刷新时间
location.href = targetURL;
setTimeout(function () {
    location.reload();
}, deferTime);

// 第二种方法,监听hashchange事件
location.href = targetURL;
window.addEventListener('hashchange', function () {
    location.reload();
}, false);

第一种方法依赖deferTime这个时间的设置,这个值设置得太小页面依然不会刷新,太大则会延迟刷新的动作,用户感觉不佳。

第二种方法监听hashchange事件(当地址的hash部分变化时会触发该事件),当地址的hash部分变化时,首先将当前页面的地址设置成目标楼层地址,此时页面并不会刷新,因此需要在事件监听函数中执行刷新动作,因为此时页面地址已经是目标楼层的地址,刷新后可以达到最初需要中提到的效果。

但是若一直监听hashchange事件,则任何一次hash变化都会执行我们设定的hashchange事件处理函数,因此这算是一个副作用,解决的方法是使用jQuery中的.one()方法来注册事件处理函数,该处理函数只会执行一次,执行完后自动取消监听。脚本中与此相关的代码片断为:

 function replySuccess() {

    // ..... 省略前文代码

    /* Fix: 回帖后地址不刷新 */
    /* 当URL Hash部分变化时, 刷新页面 */
    $(window).one('hashchange', function () {
        location.reload();
    });

    if (equalURL(location.href, url)) // 地址相同则直接刷新
        location.reload();
    else // 否则, 更换地址栏地址
        location.href = url;
}

/* 判断两个地址是否相同 */
function equalURL(lhs, rhs)
{
    /* 标准化 */
    lhs = getRelativeURL(lhs).toLowerCase();
    rhs = getRelativeURL(rhs).toLowerCase();

    return (lhs == rhs);
}