如何在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个回答

4

要监听URL的变化,请参见以下内容:

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

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

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

4

虽然这是一个老问题,但Location-bar项目非常有用,特别是在IT技术方面。

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

这是针对Node.js的,我们需要使用Browserify才能在客户端使用它。不是吗? - Anand Singh
1
不,它不是。在浏览器中工作。 - Ray Booysen
这对我的项目没有用。它是预装的,并且对于你正在使用的捆绑程序做出了很多假设。 - Seph Reed

2
最初的回答来自于这里(使用旧版JavaScript语法(不包括箭头函数,支持IE 10+)):https://dev59.com/4Gw15IYBdhLWcg3wzO1_#52809105
(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"));
  });
})();

1
在开发一个小的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而不是长度。 - Rod Lima
2
不要使用 setInterval,因为每次调用 listen(xy) 都会创建一个新的 Interval,最终会导致成千上万个间隔。 - Stef Chäser
1
你说得对,我注意到了这一点,但没有修改我的帖子,我会进行编辑。曾经我甚至因为内存泄漏而遇到过 Google Chrome 崩溃的情况。感谢您的评论。 - Alburkerk
1
window.history 的最大长度为50(至少在Chrome 80中是这样)。在此之后,window.history.length 始终返回50。当发生这种情况时,该方法将无法识别任何更改。 - Collin Krawll

1
我创建了一个与hashchange事件非常相似的事件。
// onurlchange-event.js v1.0.1
(() => {
    const hasNativeEvent = Object.keys(window).includes('onurlchange')
    if (!hasNativeEvent) {
        let oldURL = location.href
        setInterval(() => {
            const newURL = location.href
            if (oldURL === newURL) {
                return
            }
            const urlChangeEvent = new CustomEvent('urlchange', {
                detail: {
                    oldURL,
                    newURL
                }
            })
            oldURL = newURL
            dispatchEvent(urlChangeEvent)
        }, 25)
        addEventListener('urlchange', event => {
            if (typeof(onurlchange) === 'function') {
                onurlchange(event)
            }
        })
    }
})()

使用示例:

window.onurlchange = event => {
    console.log(event)
    console.log(event.detail.oldURL)
    console.log(event.detail.newURL)
}

addEventListener('urlchange', event => {
    console.log(event)
    console.log(event.detail.oldURL)
    console.log(event.detail.newURL)
})

你编写的函数回调代码本质上就是 addEventListenerdispatchEvent 所做的事情。你可以使用 addEventListener(eventType, function) 来保存回调函数,当你分发一个事件时,所有的函数都会被调用。 - aljgom
如果我今天要使用路由,我会使用crossroads.js。 - Luis Lobo

1

另一个线程中找到了一个可行的答案:

没有一个事件总是有效的,而对于大多数主要的单页应用程序,猴子补丁推送状态事件往往效果不佳。

因此,智能轮询对我来说效果最好。您可以添加任意数量的事件类型,但这些似乎对我来说非常有效。

针对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)
})

0
看一下 jQuery 的 unload 函数,它可以处理所有的事情。

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

当用户从页面导航离开时,卸载事件将发送到窗口元素。这可能意味着许多事情。用户可能单击链接以离开页面,或在地址栏中键入新的URL。前进和后退按钮将触发该事件。关闭浏览器窗口将导致触发该事件。即使页面重新加载,也会首先创建一个卸载事件。
$(window).unload(
    function(event) {
        alert("navigating");
    }
);

3
当内容使用Ajax技术嵌入页面时,URL 可能会改变而无需重新加载窗口。尽管此脚本不能检测到 URL 的更改,但对于某些在每次 URL 更改时确实需要重新加载窗口的用户仍可能有所帮助。 - Deborah
与@ranbuch的问题相同,这仅适用于不是单页应用程序的页面,并且此事件仅监视卸载窗口事件,而不是URL更改。 - ncubica

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

13
在单页应用程序的情况下,这将无法工作,因为卸载事件永远不会触发。 - ncubica

0

您每次调用时都会启动一个新的setInterval,而不取消先前的一个 - 可能您只想要一个setTimeout


0

享受!

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

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