| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- class ComprehensiveURLObserver {
- constructor(options = {}) {
- this.options = {
- trackHash: true,
- trackSearch: true,
- trackPathname: true,
- debounce: 50,
- ...options
- }
- this.observers = []
- this.lastUrl = this.getCurrentURLState()
- this.debounceTimer = null
- this.init()
- }
- init() {
- // 监听标准事件
- window.addEventListener('popstate', this.handleChange.bind(this))
- if (this.options.trackHash) {
- window.addEventListener('hashchange', this.handleChange.bind(this))
- }
- // 拦截 History API
- this.interceptHistoryMethods()
- // 监听点击事件(捕获链接点击)
- document.addEventListener('click', this.handleClick.bind(this), true)
- // 可选:轮询作为备用方案
- if (this.options.polling) {
- this.startPolling()
- }
- }
- getCurrentURLState() {
- return {
- href: window.location.href,
- pathname: window.location.pathname,
- search: window.location.search,
- hash: window.location.hash,
- origin: window.location.origin
- }
- }
- interceptHistoryMethods() {
- const methods = ['pushState', 'replaceState']
- methods.forEach(method => {
- const original = history[method]
- history[method] = (...args) => {
- const oldState = this.getCurrentURLState()
- const result = original.apply(history, args)
- this.handleChange('history', oldState)
- return result
- }
- })
- }
- handleClick(event) {
- const link = event.target.closest('a')
- if (link && link.href) {
- const targetUrl = new URL(link.href, window.location.origin)
- const currentUrl = new URL(window.location.href)
- // 如果是同源导航且不是当前页面
- if (targetUrl.origin === currentUrl.origin &&
- targetUrl.href !== currentUrl.href) {
- event.preventDefault()
- this.handleChange('click', this.getCurrentURLState())
- // 执行实际导航
- setTimeout(() => {
- window.location.href = link.href
- }, 0)
- }
- }
- }
- handleChange(type, oldState = this.lastUrl) {
- if (this.debounceTimer) {
- clearTimeout(this.debounceTimer)
- }
- this.debounceTimer = setTimeout(() => {
- const newState = this.getCurrentURLState()
- if (this.hasUrlChanged(oldState, newState)) {
- const changeInfo = {
- oldUrl: oldState.href,
- newUrl: newState.href,
- type: type,
- timestamp: Date.now(),
- details: {
- pathname: { old: oldState.pathname, new: newState.pathname },
- search: { old: oldState.search, new: newState.search },
- hash: { old: oldState.hash, new: newState.hash }
- }
- }
- this.notifyObservers(changeInfo)
- this.lastUrl = newState
- }
- }, this.options.debounce)
- }
- hasUrlChanged(oldState, newState) {
- if (oldState.href !== newState.href) return true
- if (this.options.trackPathname && oldState.pathname !== newState.pathname) return true
- if (this.options.trackSearch && oldState.search !== newState.search) return true
- if (this.options.trackHash && oldState.hash !== newState.hash) return true
- return false
- }
- startPolling() {
- setInterval(() => {
- const currentState = this.getCurrentURLState()
- if (this.hasUrlChanged(this.lastUrl, currentState)) {
- this.handleChange('polling', this.lastUrl)
- }
- }, this.options.pollingInterval || 1000)
- }
- subscribe(callback) {
- this.observers.push(callback)
- return () => this.unsubscribe(callback)
- }
- unsubscribe(callback) {
- this.observers = this.observers.filter(obs => obs !== callback)
- }
- notifyObservers(changeInfo) {
- this.observers.forEach(observer => {
- try {
- observer(changeInfo)
- } catch (error) {
- console.error('URL观察者错误:', error)
- }
- })
- }
- destroy() {
- window.removeEventListener('popstate', this.handleChange)
- window.removeEventListener('hashchange', this.handleChange)
- document.removeEventListener('click', this.handleClick)
- if (this.debounceTimer) {
- clearTimeout(this.debounceTimer)
- }
- }
- }
- // const unsubscribe = urlWatcher.subscribe((change) => {
- // console.group('URL变化详情');
- // console.log('类型:', change.type);
- // console.log('从:', change.oldUrl);
- // console.log('到:', change.newUrl);
- // console.log('路径变化:', change.details.pathname);
- // console.log('查询参数变化:', change.details.search);
- // console.log('哈希变化:', change.details.hash);
- // console.groupEnd();
- // });
- export default {
- urlWatcher: new ComprehensiveURLObserver({
- trackHash: true,
- trackSearch: true,
- trackPathname: true,
- debounce: 100
- })
- }
|