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


160

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

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

1
对于未来的访客,@ aljgom于2018年提出的新答案是最好的解决方案:stackoverflow.com/a/52809105/151503
Redzarf

Answers:


106

在现代浏览器(IE8 +,FF3.6 +,Chrome)中,您只能在上收听hashchange事件window

在某些旧的浏览器中,您需要一个计时器来不断检查location.hash。如果您使用的是jQuery,则有一个插件可以完全做到这一点。


132
据我了解,这仅适用于#号后的零件更改(因此事件名称)吗?并非完整的URL更改,问题标题似乎暗示了这一点。
NPC

13
@NPC是否有用于更改完整URL(没有定位标记)的处理程序?
Neha Choudhary 2014年

您很少需要超时事件:使用鼠标和键盘事件进行检查。
Sjeiti,

如果路径改变而不是哈希怎么办?
SuperUberDuper

@SuperUberDuper如果由于用户启动导航/单击链接等原因导致路径更改,那么您将仅看到beforeunload事件。如果您的代码启动了URL更改,那将是最好的。
phihag

92

我希望能够添加locationchange事件侦听器。经过下面的修改,我们就可以做到,像这样

window.addEventListener('locationchange', function(){
    console.log('location changed!');
})

相反,window.addEventListener('hashchange',()=>{})只有在url中的#标签后面的部分发生更改并且window.addEventListener('popstate',()=>{})并不总是起作用时,才会触发。

此修改类似于Christian的答案,修改了历史对象以添加一些功能。

默认情况下,有一个popstate事件,但也有不活动pushstate,和replacestate

这会修改这三个功能,使所有的消防自定义locationchange事件供您使用,也pushstatereplacestate活动,如果你想使用这些:

/* These are the modifications: */
history.pushState = ( f => function pushState(){
    var ret = f.apply(this, arguments);
    window.dispatchEvent(new Event('pushstate'));
    window.dispatchEvent(new Event('locationchange'));
    return ret;
})(history.pushState);

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

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

2
太好了,我需要什么谢谢
eslamb

谢谢,真的很有帮助!
Thomas Lang

1
有没有办法在IE中做到这一点?因为它不支持=>
joshuascotton

1
@joshuacotton =>是箭头函数,您可以将f =>函数fname(){...}替换为function(f){返回函数fname(){...}}
aljgom,

1
这是非常有帮助的,并且像魅力一样起作用。这应该是公认的答案。
Kunal Parekh

77

使用此代码

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

与jQuery

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

7
这必须标记为答案。这是一行,使用浏览器事件模型,不依赖于无休止的资源消耗超时
Nick Mitchell

1
我认为这是最好的答案。没有插件和最少的代码
agDev

14
不适用于非哈希网址更改,这似乎很受欢迎,例如Slack实施的更改
NycCompSci

26
如果仅在URL中存在哈希值时才触发此最佳答案?
亚伦

1
您应该使用addEventListener,而不是直接替换onhashchange值,以防其他人也想监听。
打破e

55

经过一些研究后进行编辑:

似乎我被Mozilla文档中的文档所迷住了。每当在代码中调用或时,都不会触发popstate事件(及其回调函数onpopstate)。因此,原始答案并不适用于所有情况。pushState()replaceState()

但是,有一种方法可以通过按照@ 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


11
令人难以置信的是,我们仍必须在2018
山羊

1
这适用于我的用例-但就像@goat说的一样-令人难以置信的是对此没有本地支持...
wasddd_

1
有什么争论?我将如何设置fireEvents?
SeanMC '19

2
请注意,这在许多情况下也不可靠。例如,当您单击其他Amazon产品版本(价格下方的图块)时,它不会检测到URL更改。
thdoan

2
如果不使用location.back(),则不会检测从localhost / foo更改为localhost /
baa的变化

53

使用jquery(和插件),您可以执行

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

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

否则,您将必须使用setInterval并检查hash事件(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

6
您可以使用普通的JavaScript侦听器@BergP:window.addEventListener("hashchange", hashChanged);
罗恩(Ron)

1
这样短的时间间隔对应用程序有利吗?也就是说,它是否会使浏览器在执行detect()功能时太忙?
Hasib Mahmud 2014年

4
@HasibMahmud,该代码每100毫秒执行一次相等性检查。我刚刚在浏览器中进行了基准测试,可以在1毫秒内完成500次相等性检查。因此该代码占用了我的处理能力的1/50000。我不会太担心。
z5h 2015年

24

添加哈希更改事件侦听器!

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  
}

1
弹出状态仅在您弹出状态时触发,而不是在按下状态时触发
SeanMC '19

8

这个解决方案为我工作:

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

    oldURL = window.location.href;
    setInterval(function() {
        checkURLchange(window.location.href);
    }, 1000);
}

checkURLchange();

18
这是一种非常基本的方法,我认为我们可以瞄准更高的目标。
卡尔斯·阿尔科里亚

8
尽管我同意@CarlesAlcolea的观点,但这种感觉有些陈旧,但根据我的经验,它仍然是捕获100%所有URL更改的唯一方法。
Trev14

5
@ahofmann建议(在应为注释的编辑中)更改setIntervalsetTimeout:“使用setInterval()会在一段时间后使浏览器停止,因为它将每秒创建一个对checkURLchange()的新调用。setTimeout()是正确的隔离,因为它只能被调用一次。”
divibisan

这是捕获所有URL更改的唯一解决方案,但是资源消耗迫使我寻求其他解决方案。
ReturnTable

3
或者,不要使用setTimeout@divibisan建议的方法,而是setInterval将函数移到外部。checkURLchange();也变为可选。
aristidesfl

4

尽管是一个老问题,但位置栏项目非常有用。

var LocationBar = require("location-bar");
var locationBar = new LocationBar();

// listen to all changes to the location bar
locationBar.onChange(function (path) {
  console.log("the current url is", path);
});

// listen to a specific change to location bar
// e.g. Backbone builds on top of this method to implement
// it's simple parametrized Backbone.Router
locationBar.route(/some\-regex/, function () {
  // only called when the current url matches the regex
});

locationBar.start({
  pushState: true
});

// update the address bar and add a new entry in browsers history
locationBar.update("/some/url?param=123");

// update the address bar but don't add the entry in history
locationBar.update("/some/url", {replace: true});

// update the address bar and call the `change` callback
locationBar.update("/some/url", {trigger: true});

它是针对nodeJs的,我们需要使用browserify在客户端使用它。不是吗
hack4mer

1
不,不是。在浏览器中工作
Ray Booysen

3

要收听网址更改,请参见以下内容:

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

如果您打算在某些特定条件后停止/删除侦听器,请使用此样式。

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

2

在进行chrome扩展时,我遇到了一个附加问题:有时,页面更改而不是URL。

例如,只需转到Facebook主页,然后单击“主页”按钮。您将重新加载页面,但URL不会更改(一页应用程序样式)。

99%的时间,我们都在开发网站,以便可以从Angular,React,Vue等框架中获取这些事件。

但是,在我使用Chrome扩展程序(在Vanilla JS中)的情况下,我不得不听一个事件,该事件将为每个“页面更改”触发,通常可以通过URL更改来捕获该事件,但有时并不能。

我的自制解决方案如下:

listen(window.history.length);
var oldLength = -1;
function listen(currentLength) {
  if (currentLength != oldLength) {
    // Do your stuff here
  }

  oldLength = window.history.length;
  setTimeout(function () {
    listen(window.history.length);
  }, 1000);
}

因此,基本上是将leoneckert解决方案应用于窗口历史记录,当单个页面应用程序中的页面发生更改时,该解决方案将会更改。

考虑到我们在这里只检查一个整数相等,而不是更大的对象或整个DOM,因此不是火箭科学,而是我发现的最干净的解决方案。


您的解决方案很简单,并且适用于chrome扩展程序。我建议您使用YouTube视频ID而不是长度。 stackoverflow.com/a/3452617/808901
Rod Lima,

2
您不应使用setInterval,因为每次调用listen(xy)都会创建一个新的间隔,并且最终会出现数千个间隔。
StefChäser19年

1
您是对的,我注意到之后并没有更改我的帖子,我将对其进行编辑。以前,我甚至因为内存泄漏而遇到Google Chrome崩溃的情况。感谢您的评论
Alburkerk,

window.history的最大长度为50(至少从Chrome 80开始)。此后,window.history.length始终返回50。发生这种情况时,此方法将无法识别任何更改。
Collin Krawll

1

查看jQuery卸载功能。它处理所有事情。

https://api.jquery.com/unload/

当用户离开页面导航时,将把unload事件发送到window元素。这可能意味着很多事情之一。用户可能已经单击了链接以离开页面,或者在地址栏中输入了新的URL。前进和后退按钮将触发事件。关闭浏览器窗口将导致事件被触发。即使页面重新加载也会首先创建一个卸载事件。

$(window).unload(
    function(event) {
        alert("navigating");
    }
);

2
在内容被Ajaxed的地方,URL可能会更改而不卸载窗口。该脚本不会检测到url更改,尽管它对于某些确实在每次url更改时都卸载了窗口的用户可能仍然有用。
德博拉

与@ranbuch问题相同,这仅适用于非单页应用程序的页面,并且此事件仅监视卸载窗口事件,而不监视URL更改。
ncubica

0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);

11
这在单页应用程序的上下文中将不起作用,因为卸载事件将永远不会触发
ncubica

0

下面的答案来自这里(使用旧的JavaScript语法(无箭头功能,支持IE 10+)):https : //stackoverflow.com/a/52809105/9168962

(function() {
  if (typeof window.CustomEvent === "function") return false; // If not IE
  function CustomEvent(event, params) {
    params = params || {bubbles: false, cancelable: false, detail: null};
    var evt = document.createEvent("CustomEvent");
    evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
    return evt;
  }
  window.CustomEvent = CustomEvent;
})();

(function() {
  history.pushState = function (f) {
    return function pushState() {
      var ret = f.apply(this, arguments);
      window.dispatchEvent(new CustomEvent("pushState"));
      window.dispatchEvent(new CustomEvent("locationchange"));
      return ret;
    };
  }(history.pushState);
  history.replaceState = function (f) {
    return function replaceState() {
      var ret = f.apply(this, arguments);
      window.dispatchEvent(new CustomEvent("replaceState"));
      window.dispatchEvent(new CustomEvent("locationchange"));
      return ret;
    };
  }(history.replaceState);
  window.addEventListener("popstate", function() {
    window.dispatchEvent(new CustomEvent("locationchange"));
  });
})();

0

可以执行此操作的另一种简单方法是,通过在类的名称上添加click事件,将其添加到页面上的锚标签中以检测其何时被单击,然后您可以使用window.location.href来获取网址数据您可以用来向服务器运行ajax请求。简单又容易。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.