【问题标题】: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 以检测更改?

【问题讨论】:

标签: javascript ajax fragment-identifier


【解决方案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'))
});

注意:

我们正在创建一个closureold = (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 而不是长度。 stackoverflow.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://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"));
        });
      })();
      

      【讨论】:

        【解决方案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) 保存回调,当您调度事件时,所有函数都会被调用
              • 如果我今天要在路线上使用一些东西,我会使用 crossroads.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 数据。简单易行。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2010-09-15
                  • 1970-01-01
                  • 2011-08-11
                  • 2015-01-21
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多