上一篇文章中,我在LeetCode抓取题解的油猴脚本中新增了一个功能,在题解页面,单独显示一个按钮,点击按钮之后可以抓取当前页面的题解内容。

到这里就产生了一个问题,如何判断当前页面是一个题解的页面呢,举一个简单的题解页面的URL的例子

https://leetcode.cn/problems/find-duplicate-subtrees/solution/by-cheungq-6-fkom/

我们可以看到,除去前面的“https://”协议、“leetcode.cn”域名,后面的“/problems/find-duplicate-subtrees/solution/by-cheungq-6-fkom/”部分就是我们需要解析的部分,这部分内容存在于“window.location.pathname”之中,我们可以随时很方便的获取到,只需判断下是不是

/problems/{Question_Slug}/solution/{Solution_Slug}/

这样的格式即可,当然如果你非常熟悉正则的方式,也可以用正则匹配判断。这样我们就可以知道这个页面是不是一个题解的页面了,如果当前页面是一个题解页面,则我们进行控制显示一个抓取当前题解的按钮

在解决了题解页面判断之后,重新回到LeetCode网站看下,这时会发现我们遇到了新的问题。LeetCode的题目和题解页面使用的是无刷新的方法来渲染页面的,点击题目或者题解内容,页面进行渲染,同时Url进行变更,页面不进行刷新操作。这样的方式大大提升了用户的浏览体验。而很明显我们可以看到使用的不是hash模式的URL,而是history模式的实现。

hash模式的URL是在URL结尾拼接上“#”符及相关参数来实现的,变更URL中“#”后面的内容不会引发页面刷新操作,“#”原本是作为页面锚点的功能存在的

熟悉前端的小伙伴应该对hash模式history模式这两个概念非常了解了,基本属于面试必问基础题系列,如果不知道的可以再自行百度一下

那么在有了以上的基础认知之后,我们就开始着手对history模式URL变更进行监听操作

不同于hash模式的URL,当hash模式的URL发生变更的时候,会触发window.onhashchange事件(参见Window:hashchange event),这样的话我们只要监听window.onhashchange事件就可以了知道URL变更了。而history模式下,是通过history对象来操作实现的,根据MDN文档上的说明信息,在history操作的时候会触发popstate事件,不过下面有一条额外的备注

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/popstate_event

调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件

在力扣的网页上试了下window.addEventListener('popstate',function)相关的操作,确实在点击了之后没有反应。

那么到这里我们又有了另一个思路,参照之前的【油猴脚本】关于用油猴脚本爬取考试题库这件事文章中使用的手法,我们自己建个replaceState和pushState方法替换掉原来history中的replaceState和pushState方法,在我们自己建的方法中调用原来浏览器的对应方法,并向外抛出一个相应的事件,于是就有了如下代码

let _historyWrap = function(type) {
    const orig = history[type];
    return function() {
        const rv = orig.apply(this, arguments);
        publishCustomEvent(type,arguments)
        return rv;
    };
};
history.pushState = _historyWrap('pushState');
history.replaceState = _historyWrap('replaceState');

其中的publishCustomEvent方法是调用的之前的window.CustomEvent浏览器自定义事件文章中写的自定义浏览器事件相关的代码。

这样,我们只要再监听下这里抛出的pushState和replaceState事件就可以了,代码如下

    window.addEventListener('pushState',(e)=>{
        //do something
        //判断当前url是否是题解页面
    },true)