如何在JavaScript中检测URL的哈希部分之后是否已更改

253

如何在JavaScript中检查URL是否已更改?例如,像GitHub这样使用AJAX的网站会在#符号后附加页面信息以创建唯一的URL而无需重新加载页面。检测URL更改的最佳方法是什么?

  • 是否再次调用onload事件?
  • 是否有URL的事件处理程序?
  • 还是必须每秒检查一次URL以检测更改?

2
对于未来的访问者,2018年@aljgom提供的新答案是最佳解决方案:https://dev59.com/4Gw15IYBdhLWcg3wzO1_#52809105 - Redzarf
这是一个使用新构建更好的答案。https://dev59.com/sVQJ5IYBdhLWcg3wsIBY - Jahan Zinedine
这是一个更好的答案,使用了新的构造 https://stackoverflow.com/questions/53303519/detect-an-url-change-in-a-spa - undefined
21个回答

244

我希望能够添加locationchange事件监听器。在下面的修改后,我们就可以这样做了。

window.addEventListener('locationchange', function () {
    console.log('location changed!');
});
相比之下,window.addEventListener('hashchange',() => {}) 只有在 URL 中井号后的部分更改时才会触发,而 window.addEventListener('popstate',() => {}) 则不总是有效。
这种修改(类似于 Christian 的答案),修改了历史对象以添加一些功能。
在这些修改之前,默认情况下存在一个 popstate 事件,但没有 pushstatereplacestate 事件。
这修改了这三个函数,使它们都为您触发自定义的 locationchange 事件,如果您想使用这些事件,则还包括 pushstatereplacestate 事件。
这些是修改内容:
(() => {
    let oldPushState = history.pushState;
    history.pushState = function pushState() {
        let ret = oldPushState.apply(this, arguments);
        window.dispatchEvent(new Event('pushstate'));
        window.dispatchEvent(new Event('locationchange'));
        return ret;
    };

    let oldReplaceState = history.replaceState;
    history.replaceState = function replaceState() {
        let ret = oldReplaceState.apply(this, arguments);
        window.dispatchEvent(new Event('replacestate'));
        window.dispatchEvent(new Event('locationchange'));
        return ret;
    };

    window.addEventListener('popstate', () => {
        window.dispatchEvent(new Event('locationchange'));
    });
})();

请注意,我们正在创建一个闭包,将旧函数保存为新函数的一部分,以便在调用新函数时随之调用旧函数。


1
有没有办法在IE中实现这个?因为它不支持“=>”。 - joshuascotton
1
@joshuascotton 是的,有的!我会尝试在这个答案中添加它。 - Thomas Lang
2
@joshuacotton => 是箭头函数,你可以使用function(f){ return function fname(){...} }替代f => function fname(){...} - aljgom
@JulienD 'locationchange'是一个自定义事件,如果没有这段代码,它就不存在。如果没有进行修改,它不会随着URL的更改而默认触发。 - aljgom
半夜的时候,我得打开电脑并登录StackOverflow来给这个点赞。 - undefined
显示剩余3条评论

145

在现代浏览器(IE8+,FF3.6+,Chrome)中,您可以在window上侦听hashchange事件。

在一些旧版浏览器中,您需要一个定时器来不断检查location.hash。如果您正在使用jQuery,则有一个插件可以完成这个操作。

示例

下面的示例会取消任何URL更改,仅保留滚动:

<script type="text/javascript">
  if (window.history) {
    var myOldUrl = window.location.href;
    window.addEventListener('hashchange', function(){
      window.history.pushState({}, null, myOldUrl);
    });
  }
</script>

请注意,上述使用的history-API可在Chrome、Safari、Firefox 4+和Internet Explorer 10pp4+中使用。


159
据我理解,这仅适用于在#符号后更改的部分(因此是事件名称)?而不适用于完整的URL更改,正如问题标题所暗示的那样。 - NPC
15
@NPC 有没有处理完整URL变化(不包括锚点标签)的处理程序? - Neha Choudhary
你很少需要超时事件:使用鼠标和键盘事件进行检查。 - Sjeiti
1
如果路径改变而哈希值不变怎么办? - SuperUberDuper
如果路径因为用户发起导航/点击链接等而更改,则只会看到“beforeunload”事件。如果您的代码启动了URL更改,则最清楚不过了。 - phihag
1
如果您想监视URL GET参数上的任何新内容或更新,该怎么办?谢谢。 - Alberto S.

99
window.onhashchange = function() { 
     //code  
}

window.onpopstate = function() { 
     //code  
}
或者
window.addEventListener('hashchange', function() { 
  //code  
});

window.addEventListener('popstate', function() { 
  //code  
});

使用jQuery

$(window).bind('hashchange', function() {
     //code
});

$(window).bind('popstate', function() {
     //code
});

10
必须标记此答案。这是一行代码,使用浏览器事件模型,并且不依赖于消耗无尽资源的超时。 - Nick Mitchell
19
不适用于非哈希URL更改,这似乎非常流行,例如Slack所实现的那种。 - NycCompSci
36
如果只有在 URL 中存在哈希标识符时才会触发,那么这怎么能成为最佳答案呢? - Aaron
5
你应该使用addEventListener而不是直接替换onhashchange值,以防其他东西也想监听。 - broken-e
用户激活历史记录导航时的“pageshow”。 - Monday Fatigue

80

在进行一些研究后编辑:

某种程度上看起来我被 Mozilla 文档上的说明所欺骗了。当代码中调用 pushState()replaceState() 时,popstate 事件(及其回调函数 onpopstate)并不会触发(参见此处)。因此,原回答并不适用于所有情况。

然而,有一种方法可以通过@alpha123 的函数猴子补丁绕过这个问题:

var pushState = history.pushState;
history.pushState = function () {
    pushState.apply(history, arguments);
    fireEvents('pushState', arguments);  // Some event-handling function
};

翻译后的回答

考虑到这个问题的标题是"如何检测URL更改",如果您想知道完整路径何时改变(而不仅仅是哈希锚点),则可以监听popstate事件:

window.onpopstate = function(event) {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};

Mozilla 开发者文档中有 popstate 的参考资料

目前(2017年1月)全球92%的浏览器支持 popstate


20
2018年了居然还需要使用这种hack方法,真是令人难以置信。 - goat
2
什么参数?我该如何设置fireEvents? - SeanMC
3
请注意,这种方法在许多情况下也是不可靠的。例如,当您单击不同的亚马逊产品变体(价格下面的标签)时,它无法检测到网址更改。 - thdoan
3
如果不使用location.back(),这将无法检测到从localhost/foo到localhost/baa的更改。 - SuperUberDuper
2
令人难以置信,我们在2022年必须采用这样的hack手段。 - Kevin Farrugia
显示剩余4条评论

53

使用jQuery(及其插件)可以实现:

$(window).bind('hashchange', function() {
 /* things */
});

http://benalman.com/projects/jquery-hashchange-plugin/

否则,您将必须使用 setInterval 并检查哈希事件的更改(window.location.hash)

更新!一个简单的草稿

function hashHandler(){
    this.oldHash = window.location.hash;
    this.Check;

    var that = this;
    var detect = function(){
        if(that.oldHash!=window.location.hash){
            alert("HASH CHANGED - new has" + window.location.hash);
            that.oldHash = window.location.hash;
        }
    };
    this.Check = setInterval(function(){ detect() }, 100);
}

var hashDetection = new hashHandler();

我能检测(window.location)的变化并处理它吗?(不使用jQuery) - BergP
8
你可以 @BergP,使用纯JavaScript监听器:window.addEventListener("hashchange", hashChanged); - Ron
1
这么短的时间间隔对这个应用程序好吗?也就是说,它不会使浏览器在执行 detect() 函数时过于繁忙吗? - Hasib Mahmud
4
@HasibMahmud,这段代码每100毫秒进行1次等式检查。我刚在浏览器中进行了基准测试,发现我可以在不到1毫秒的时间内执行500个等式检查。因此,这段代码使用了我的处理能力的1/50000。我不会太担心。 - Mark Bolusmjak

28

添加一个哈希变化事件监听器!

window.addEventListener('hashchange', function(e){console.log('hash changed')});

或者,监听所有URL更改:

window.addEventListener('popstate', function(e){console.log('url changed')});

这比下面的代码好,因为只能在window.onhashchange中存在一件事情,否则你可能会覆盖其他人的代码。

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}

4
只有在弹出(pop)状态时才会触发“pop state”,而不是在推入(push)状态时。 - SeanMC
2
这仅在使用浏览器的后退和前进按钮导航时有效,即在许多情况下完全无用。 - Operator

19

这个解决方案对我有效:

function checkURLchange(){
    if(window.location.href != oldURL){
        alert("url changed!");
        oldURL = window.location.href;
    }
}

var oldURL = window.location.href;
setInterval(checkURLchange, 1000);

47
这是一种相当基础的方法,我认为我们可以有更高的目标。 - Neithan Max
16
虽然我同意@CarlesAlcolea的观点,认为这种方法有些老旧,但根据我的经验,它仍然是捕获100%所有url变更的唯一方法。 - Trev14
6
@ahofmann建议(这应该是一条评论而不是编辑)将setInterval更改为setTimeout:“使用setInterval()会在一段时间后使浏览器停止,因为它会每秒创建一个新的对checkURLchange()的调用。 setTimeout()是正确的解决方案,因为它仅被调用一次。” - divibisan
3
或者,可以像@divibisan建议的那样,将setTimeout换成将setInterval移到函数之外。checkURLchange();也变成了可选的。 - aristidesfl
3
我同意,在所有答案中,这是我能够捕捉到所有URL更改的唯一方法。 - Diego Fortes
显示剩余2条评论

19

适用于Chrome 102+(2022-05-24)

navigation.addEventListener("navigate", e => {
  console.log(`navigate ->`,e.destination.url)
});

API references WICG/navigation-api


1
太棒了!对于Chrome来说,这应该是最佳答案,因为它可以捕捉到所有的URL变化! - undefined
真不敢相信我得往下滚这么远才能找到一个更新的答案! - undefined

9

当单击链接并重定向到同一域上的不同页面时,这些解决方案似乎都无效。因此,我自己创造了解决方案:

let pathname = location.pathname;
window.addEventListener("click", function() {
    if (location.pathname != pathname) {
        pathname = location.pathname;
        // code
    }
});

编辑:您还可以检查popstate事件(如果用户返回页面)

window.addEventListener("popstate", function() {
    // code
});

祝一切顺利,

微积分


7
如果您发现窗口事件没有起作用(如我的情况),也可以使用监视根元素(非递归)的MutationObserverMutationObserver是一个很好的选择。
// capture the location at page load
let currentLocation = document.location.href;

const observer = new MutationObserver((mutationList) => {
  if (currentLocation !== document.location.href) {
    // location changed!
    currentLocation = document.location.href;

    // (do your event logic here)
  }
});

observer.observe(
  document.getElementById('root'),
  {
    childList: true,

    // important for performance
    subtree: false
  });

这可能并不总是可行,但通常情况下,如果URL改变,根元素的内容也会随之改变。

理论上来说,我没有进行过性能分析,但这种方法的开销应该比定时器小,因为Observer模式通常是这样实现的:当发生更改时,它只需循环订阅即可。在这里,我们只添加了一个订阅。而定时器则需要频繁检查以确保事件立即在URL更改后触发。

此外,这种方法的可靠性比定时器更高,因为它消除了时间问题。


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