如何检测 JavaScript 中的哈希后 URL 是不是已更改

Posted

技术标签:

【中文标题】如何检测 JavaScript 中的哈希后 URL 是不是已更改【英文标题】:How to detect if URL has changed after hash in JavaScript如何检测 JavaScript 中的哈希后 URL 是否已更改 【发布时间】:2011-09-17 10:25:47 【问题描述】:

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

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

【问题讨论】:

对于未来的访问者,@aljgom 从 2018 年开始的新答案是最佳解决方案:***.com/a/52809105/151503 【参考方案1】:

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

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

相比之下,window.addEventListener('hashchange',()=>) 只会在 url 中标签后的部分发生更改时触发,而window.addEventListener('popstate',()=>) 并不总是有效。

这个修改,类似于Christian's answer,修改了历史对象,增加了一些功能。

默认情况下,在这些修改之前,有一个popstate 事件,但没有pushstatereplacestate 的事件。

这会修改这三个函数,以便都触发一个自定义的locationchange 事件供您使用,如果您想使用它们,还可以触发pushstatereplacestate 事件。

这些是修改:

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'))
);

注意:

我们正在创建一个closure,old = (f=>function new()f();...)(old)old 替换为 new 函数,该函数包含之前保存的 oldold 目前未运行,但它将在new 内部运行)

【讨论】:

太好了,我需要的谢谢 有没有办法在 IE 中做到这一点?因为它不支持 => @joshuascotton 是的,有!我会尝试将其添加到此处的答案中 @joshuacotton => 是一个箭头函数,可以将 f => function fname()... 替换为 function(f) return function fname()... 这很有帮助,而且效果很好。这应该是公认的答案。【参考方案2】:

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

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

示例

下面我撤消任何 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+ 中可用

【讨论】:

据我了解,这仅适用于更改 # 符号后的部分(因此事件名称)?而不是完整的 URL 更改,正如问题标题所暗示的那样。 @NPC 任何用于完整 URL 更改的处理程序(无锚标记)? 您很少需要超时事件:使用鼠标和键盘事件进行检查。 如果路径改变了,而不是哈希怎么办? @SuperUberDuper 如果路径因用户启动导航/单击链接等而改变,那么您将只会看到beforeunload 事件。如果您的代码启动了 URL 更改,它最清楚。【参考方案3】:
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
);

【讨论】:

这必须标记为答案。一条线,使用浏览器事件模型,不依赖无休止的资源消耗超时 不适用于非哈希 url 更改,这似乎非常流行,例如 Slack 实现的更改 如果仅在 url 中有哈希时触发,这是最佳答案吗? 您应该使用 addEventListener 而不是直接替换 onhashchange 值,以防其他人也想听。 pageshow 用于用户激活的历史导航【参考方案4】:

经过一番研究后编辑:

不知何故,我似乎被 Mozilla 文档中的文档愚弄了。每当在代码中调用pushState()replaceState() 时,popstate 事件(及其回调函数onpopstate)就是not triggered。因此,原始答案并不适用于所有情况。

不过monkey-patching the functions according to @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));
;

Reference for popstate in Mozilla Docs

目前(2017 年 1 月)全球有 support for popstate from 92% 个浏览器。

【讨论】:

令人难以置信的是,我们在 2018 年仍然必须诉诸此类黑客攻击。 这适用于我的用例 - 但就像 @goat 所说 - 令人难以置信的是,没有对此的原生支持...... 什么论据?我将如何设置 fireEvents? 请注意,这在很多情况下也是不可靠的。例如,当您点击不同的亚马逊产品变体(价格下方的图块)时,它不会检测到 URL 更改。 如果不使用 location.back(),这不会检测到从 localhost/foo 到 localhost/baa 的变化【参考方案5】:

使用 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,使用普通的javascript监听器:window.addEventListener("hashchange", hashChanged); 这么短的时间间隔对应用有好处吗?也就是说,它不会让浏览器忙于执行detect()函数吗? @HasibMahmud,该代码每 100 毫秒执行 1 次相等性检查。我刚刚在我的浏览器中进行了基准测试,我可以在 1 毫秒内进行 500 次相等检查。所以该代码使用了我处理能力的 1/50000。我不会太担心。【参考方案6】:

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

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  

【讨论】:

弹出状态仅在您弹出一个状态时触发,而不是推送一个 这仅在使用浏览器的后退和前进按钮导航时有效,即在许多情况下完全没用。【参考方案7】:

这个解决方案对我有用:

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


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

【讨论】:

这是一种比较初级的方法,我想我们可以瞄准更高的目标。 虽然我同意@CarlesAlcolea 的观点,这感觉很陈旧,但根据我的经验,它仍然是捕获 100% 的所有 url 更改的唯一方法。 @ahofmann 建议(在应该是评论的编辑中)将 setInterval 更改为 setTimeout:“使用 setInterval() 会在一段时间后让浏览器停止,因为它会每秒创建一个对 checkURLchange() 的新调用。setTimeout() 是正确的解决方案,因为它只被调用一次。" 或者不要像@divibisan 建议的那样使用setTimeout,而是将setInterval 移到函数之外。 checkURLchange(); 也成为可选的。 我同意,从所有答案中,这是我能够捕获所有 URL 更改的唯一方法。【参考方案8】:

虽然是老问题,但Location-bar 项目非常有用。

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才能在客户端使用它。不是吗? 不,不是。在浏览器中工作 这对我的项目不起作用。它是预先捆绑的,并对您使用的捆绑器做了很多假设。【参考方案9】:

要监听 url 变化,见下文:

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

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

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

【讨论】:

【参考方案10】:

当单击链接将您重定向到同一域上的不同页面时,这些似乎都不起作用。因此,我制定了自己的解决方案:

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

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

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

祝你好运,

微积分

【讨论】:

【参考方案11】:

在做一些 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 而不是长度。 ***.com/a/3452617/808901 您不应该使用 setInterval,因为每次调用 listen(xy) 时都会创建一个新的 Interval,最终会产生数千个间隔。 你说得对,我注意到之后并没有更改我的帖子,我将对其进行编辑。早在那时,我什至遇到了由于 RAM 泄漏而导致 Google Chrome 崩溃的情况。谢谢你的评论 window.history 的最大长度为 50(至少从 Chrome 80 开始)。在那之后,window.history.length 总是返回 50。发生这种情况时,此方法将无法识别任何更改。【参考方案12】:

下面的答案来自这里(使用旧的 javascript 语法(无箭头功能,支持 IE 10+)): https://***.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"));
  );
)();

【讨论】:

【参考方案13】:

如果没有任何窗口事件对您有用(因为在我的情况下它们不是),您还可以使用查看根元素的MutationObserver(非递归)。

// 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 发生变化,根元素的内容也会发生变化。

我没有分析过,但理论上这比计时器的开销要少,因为观察者模式通常是这样实现的,因此它只是在发生更改时循环遍历订阅。我们在这里只添加了一个订阅。另一方面,计时器必须非常频繁地检查,以确保在 URL 更改后立即触发事件。

此外,这很有可能比计时器更可靠,因为它消除了计时问题。

【讨论】:

【参考方案14】:

在separate thread 中找到了有效的答案:

没有一个事件总是有效的,猴子修补 pushState 事件对于大多数主要的 SPA 来说都是命中注定的。

所以智能投票对我来说是最有效的。您可以添加任意数量的事件类型,但这些似乎对我来说做得很好。

为 TS 编写,但易于修改:

const locationChangeEventType = "MY_APP-location-change";

// called on creation and every url change
export function observeUrlChanges(cb: (loc: Location) => any) 
  assertLocationChangeObserver();
  window.addEventListener(locationChangeEventType, () => cb(window.location));
  cb(window.location);


function assertLocationChangeObserver() 
  const state = window as any as  MY_APP_locationWatchSetup: any ;
  if (state.MY_APP_locationWatchSetup)  return; 
  state.MY_APP_locationWatchSetup = true;

  let lastHref = location.href;

  ["popstate", "click", "keydown", "keyup", "touchstart", "touchend"].forEach((eventType) => 
    window.addEventListener(eventType, () => 
      requestAnimationFrame(() => 
        const currentHref = location.href;
        if (currentHref !== lastHref) 
          lastHref = currentHref;
          window.dispatchEvent(new Event(locationChangeEventType));
        
      )
    )
  );

用法

observeUrlChanges((loc) => 
  console.log(loc.href)
)

【讨论】:

【参考方案15】:

看一下 jQuery 的卸载功能。它处理所有事情。

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

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

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

【讨论】:

在内容被 Ajaxed 插入的地方,url 可能会在没有卸载窗口的情况下发生变化。此脚本不会检测到 url 更改,尽管它可能对某些在每次 url 更改时都会卸载窗口的用户仍有帮助。 同@ranbuch问题,只针对非单页应用的页面,该事件只关注卸载窗口事件,不关注url变化。【参考方案16】:
window.addEventListener("beforeunload", function (e) 
    // do something
, false);

【讨论】:

这在单页应用程序的上下文中不起作用,因为卸载事件永远不会触发【参考方案17】:

您在每次通话时都开始一个新的setInterval,而不取消前一个 - 可能您只是想拥有一个setTimeout

【讨论】:

【参考方案18】:

我创建了这个,只需将函数添加为参数,只要链接有任何更改,它就会运行返回旧网址和新网址的函数

我创建了这个,只需将函数添加为参数,只要链接有任何更改,它就会运行返回旧网址和新网址的函数

// on-url-change.js v1 (manual verification)
let onUrlChangeCallbacks = [];
let onUrlChangeTimestamp = new Date() * 1;
function onUrlChange(callback)
    onUrlChangeCallbacks.push(callback);
;
onUrlChangeAutorun();
function onUrlChangeAutorun()
    let oldURL = window.location.href;
    setInterval(function()
        let newURL = window.location.href;
        if(oldURL !== newURL)
            let event = 
                oldURL: oldURL,
                newURL: newURL,
                type: 'urlchange',
                timestamp: new Date() * 1 - onUrlChangeTimestamp
            ;
            oldURL = newURL;
            for(let i = 0; i < onUrlChangeCallbacks.length; i++)
                onUrlChangeCallbacks[i](event);
            ;
        ;
    , 25);
;

【讨论】:

您为函数回调编写的代码本质上是addEventListenerdispatchEvent 所做的。您使用addEventListener(eventType, function) 保存回调,当您调度事件时,所有函数都会被调用 如果我今天要在路线上使用一些东西,我会使用 cros-s-roads.js【参考方案19】:

享受吧!

var previousUrl = '';
var observer = new MutationObserver(function(mutations) 
  if (location.href !== previousUrl) 
      previousUrl = location.href;
      console.log(`URL changed to $location.href`);
    
);

【讨论】:

【参考方案20】:

另一个简单的方法是添加一个点击事件,通过类名到页面上的锚标签来检测它何时被点击,然后你现在可以使用 window.location.href 来获取可用于向服务器运行 ajax 请求的 url 数据。简单易行。

【讨论】:

以上是关于如何检测 JavaScript 中的哈希后 URL 是不是已更改的主要内容,如果未能解决你的问题,请参考以下文章

如何检测 JavaScript 字符串中的 URL 并将其转换为链接?

如何在不刷新页面的情况下使用 JavaScript 从 window.location (URL) 中删除哈希?

Javascript/jQuery - 使用正则表达式解析字符串中的主题标签,URL 中的锚点除外

使用 JavaScript 检测文本中的 URL

检查当前 URL 是不是包含哈希 [重复]

Javascript/jQuery 仅在浏览器后退/前进按钮单击时检测哈希更改