JavaScript 如何监听页面跳转 `window.location?.href` ?

现在有个需求,就是监听网站内跳转的所有链接并加上自定义参数。
比如跳转至abc.com,就加以拦截并改为abc.com?rerf=xxx

目前页面内的跳转共有3种方式

  • a(✅)
  • window.open()(✅)
  • window.location?.href(❌)

现在已经实现了前两种的监听,剩下最后一个location跳转时无法监听到的

window.onbeforeunload这个方法不太适用,无法阻止
参考如下的帖子,方法基本都失效。目前测试浏览器版本chrome版本 97.0.4692.99
event-when-window-location-href-changes

阅读 23.7k
5 个回答
  • a(✅)
  • window.open()(✅)
  • location?.href(✅)只能是包装处理下 => location.href = linkEvent.formatNavigatedUrl("https://www.xxxx.com");
class LinkEvent {
    constructor() {
      this.init();
    }
    init() {
      this.setReferrer();
      this.listenLinkClick();
      this.listenWindowOpen();
      window.addEventListener("popstate", () => this.setReferrer());
    }
    getReferrer() {
      //读取当前来源的内容
      return this.referrer
    }
    /**
     * 设置引荐网址来源
     */
    setReferrer() {
        //根据业务自行处理
        this.referrer = ''
    }
    /**
     * 监听a链接的点击
     */
    listenLinkClick() {
      document.body.addEventListener(
        "click",
        function (event) {
          // 兼容处理
          var target = event.target || event.srcElement;
          // 判断是否匹配目标元素
          if (target.nodeName.toLocaleLowerCase() === "a") {
            // 对捕获到的 a 标签进行处理
            if (event.preventDefault) {
              event.preventDefault();
            } else {
              window.event.returnValue = true;
            }
            // 处理完 a 标签的内容,重新触发跳转,根据原来 a 标签页 target 来判断是否需要新窗口打开
            this.choosePush(target);
          }
        }.bind(this)
      );
    }
    /**
     * 判断是否需要新窗口打开
     */
    choosePush(el) {
      const target = el.getAttribute("target");
      const href = el.getAttribute("href");
      const url = this.formatNavigatedUrl(href);
      if (target === "_blank") {
        window.open(url);
      } else {
        window.location.href = url;
      }
    }
    /**
     * 监听window.open方法的调用
     */
    listenWindowOpen() {
      var orgOpen = window.open;
      window.open = function (...args) {
        args[0] = this.formatNavigatedUrl(args[0], this.getQueryVariable(args[0]));
        return orgOpen(...args);
      }.bind(this);
    }
    /**
     * 监听window.location方法的调用(无法实现)
     */
    listenWindowLocation() {
      //因为window.location方法为原生属性,不允许重写
      //使用 Object.defineProperty以及Proxy都不能监听到set行为
      //只能是进行全局调用$formatNavigatedUrl进行已有url替换
    }
    /**
     * 处理网址并添加参数
     * @param {string} url https://www.baidu.com
     * @returns https://www.baidu.com?name=webapp
     */
    formatNavigatedUrl(url, params = {}) {
      const origin = url.split("?")[0];
      const searchParams = this.getQueryVariable(url)
      searchParams['referrer'] = this.getReferrer()
      //处理自定义参数
      Object.keys(params).forEach(key => {
        searchParams[key] = params[key]
      })
      let formatParams = this.objetToQuery(searchParams)
      return (url = `${origin}${formatParams?`?${formatParams}`:''}`);
    }
    /**
     * 获取url参数
     * @param {string} url 
     * @param {string} variable 需要取值的key,不传为取所有
     */
    getQueryVariable(url, variable) {
      var query = url.split("?")[1] || '';
      var vars = query.split("&");
      let params = {}
      for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split("=");
        (pair[0] && pair[1]) && (params[pair[0]] = pair[1].split('#')[0].split('/')[0])
        if (variable && pair[0] == variable) {
          //特殊处理 http://localhost:9528/nested/menu2?ref=34343343#/dashboard
          //这种url截取的参数会带34343343#/dashboard。所以要截掉#后面的多余部分
          return pair[1].split('#')[0].split('/')[0];
        }
      }
      return variable ? '' : params;
    }
    /**
     * 对象转url参数
     * @param {Object} searchParams 
     */
    objetToQuery(searchParams) {
      let arr = Object.keys(searchParams)
      return arr.reduce((p, c, index) => {
        p += `${c}=${searchParams[c]}${index < arr.length - 1 ? '&' : ""}`
        return p
      }, '')
    }
  }
  window.LinkEvent = LinkEvent;
  
  export default LinkEvent;
const temp = location;
window.location = xx // 给location替换个东西,然后自己添加参数,最后再调用href

location.href = "xxxx";

用window.onload监听,判断当前是否是abc.com

如果是你自己设计开发的网站,其实可以杜绝有通过javascript调用window.location?.href 的情况啊,所以就不需要监听了。

或者说,你其实对自己开发的网站,应该可以控制如何构建(重设)window.location?.href以达到你需要的效果。

我以前尝试过给location的跳转加钩子,但是都失败了。

  • 一楼说的替换location的方法肯定是不行的,替换后立即跳转;
  • 使用 Proxy 自然也不行,因为也需要替换;
  • 使用 Object.definePropertyObject.defineProperties 自定义的 setter 没有效果,不管是定义 window + 'location' 还是 window.location + 'href' 都一样。

我觉得可行的方法怕是只有:

  1. 服务端重定向;
  2. iframe二次跳转(当然这多少有点影响体验)。
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题