周末开发了一个在 GitHub 中给 repo 增加自定义备注的 chrome 扩展。

开发这个扩展的原因是我在 GitHub 中所 star 的项目实在太多了(截止目前 671 个),有的项目过个几天回来看就忘了为什么 star 了,有的轮子想找的时候发现忘记叫什么了,这么多一个个找实在浪费时间。于是我一直在想有这么个工具,可以自定义对 GitHub 中的项目进行备注,然后可以根据备注进行搜索,于是便有了这个扩展。

如需安装体验请点击 GitHub Remarks 进行安装,源码移步 github-remarks(没啥参考价值)。

当然本文并不是讲这个扩展的制作过程,而是在这过程中碰到的一个问题。

以我的 GitHub 账户举例,在 https://github.com/hanzichi?tab=stars 页面,我需要插入一些 dom,使得页面如下:

乍一想,似乎很简单,在这个页面插入 content.js 就行,在 manifest.json 中增加配置:

{
  "matches": ["*://github.com/.*tabs=stars"],
  "js": ["content-scripts/repoDetail.js"],
  "css": [],
  "run_at": "document_end"
 }

但是问题来了,直接打开页面 https://github.com/hanzichi?tab=stars 并没有问题,但是如果从 https://github.com/hanzichi 点击跳转到 https://github.com/hanzichi?tab=stars,因为 GitHub 用了 pjax 技术,content.js 其实应该加载在页面 https://github.com/hanzichi 的,但是那个页面并没有参照的 dom 结构(好把我们需要的 dom 插入)。所以问题似乎就转为了,在 pjax 页面,如何能够监听到页面 url 的变化

首先一个很简单的方案是,弄个定时器循环监听,但是太耗费性能了,pass

因为页面是 pjax 跳转,所以 hashchange 这样的事件自然也用不上;而 pushstate 事件是监听浏览器前进后退的,所以也并不符合场景

有个想法很好,重写 pushState 事件(代码来自 How to detect when HTML5's history.pushState() is called?):

(function(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({state: state});
        }
        // ... whatever else you want to do
        // maybe call onhashchange e.handler
        return pushState.apply(history, arguments);
    }
})(window.history);

但是因为 chrome 扩展和源代码之间并不共享作用域,所以重写的 history.pushState 方法在原来页面其实并没有改变,所以也没什么卵用

然后从 chrome API 入手,发现有个 chrome API 可以检测当前 url 改变的 tab(参考 Chrome extension WebNavigation listener for hash change):

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
  if (changeInfo.url) {
      console.log('Tab %d got new URL: %s', tabId, changeInfo.url)
  }
})

这段代码是在 background.js 中,然后一旦侦测到 URL 变化,background.js 和 content.js 通信告知即可。

但是,但是,也有问题,url 一改变确实能监听到,但是 chrome 扩展需要在相应 dom ready 后才能作用(才能在之前 dom 的基础上插入新的 dom),你无法检测到什么时候 dom 已经准备就绪了,所以在这里加个定时器延迟作用是可行的,时间需要自己估算下

这个方法不是很优雅,最后想到,pjax 的过程中,dom 肯定有变化,所以监听 dom 变化是否可行?答案是可以的:

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function(mutations, observer) {
  let url = location.href
  let p = /.*\/\/github.com\/.*\?tab=stars.*/

  if (p.test(url)) {
    initStarsPage()
  }
})

observer.observe(document.getElementById('js-pjax-container'), {
  childList: true
})

$(document).ready(() => {
  let url = location.href
  let p = /.*\/\/github.com\/.*\?tab=stars.*/

  if (p.test(url)) {
    initStarsPage()
  }
})

写到最后,其实并不是监听了 URL,而是监听了 dom 的变化。

最后的最后,意外在 so 上找到 这个答案 似乎就是对应我的问题,大概看了下,除了我举例的几个方案外,其实我遗漏了最简单的一个方案,既然页面使用了 pjax,那么直接监听 pjax:complete 事件即可:

$(document).on('pjax:complete', function() {
  let url = location.href
  let p = /.*\/\/github.com\/.*\?tab=stars.*/

  if (p.test(url)) {
    initStarsPage()
  }
})

突然让人感觉,有时候 "真相" 往往总是那么简单。

对于单页应用中如何监听 URL 变化的思考的更多相关文章

  1. 009——VUE中watch监听属性变化实现类百度搜索栏功能

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. Angular.js中使用$watch监听模型变化

    $watch简单使用 $watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你. $watch(watchExpression, listener, objectEqua ...

  3. 新建Oracle数据库时,提示使用database control配置数据库时,要求在当前oracle主目录中配置监听程序

    新建一个oracle数据库时,当提示使用database control配置数据库时,要求在当前oracle主目录中配置监听程序等字样的时候,问题是那个监听的服务没有启动,解决方法如下: 打开cmd命 ...

  4. Android中如何监听GPS开启和关闭

    转自 chenming 原文 Android中如何监听GPS开启和关闭   摘要: 本文简单总结了如何监听GPS开关的小技巧 有时需要监听GPS的开关(这种需求并不多见).实现的思路是监听代表 GPS ...

  5. Nginx 中 fastcgi_pass 监听端口 unix socket和tcp socket差

    Nginx 中 fastcgi_pass 监听端口 unix socket和tcp socket差别   Nginx连接fastcgi的方式有2种:unix domain socket和TCP,Uni ...

  6. oracle 11g在安装过程中出现监听程序未启动或数据库服务未注册到该监听程序

    15511477451 原文 oracle 11g在安装过程中出现监听程序未启动或数据库服务未注册到该监听程序? 环境:win7 64位系统.oracle11g数据库 问题描述:在win7 64位系统 ...

  7. Android 关于ListView中按钮监听的优化问题(方法二)

    关于ListView中按钮监听的优化问题(方法一)地址: http://www.cnblogs.com/steffen/p/3951901.html 之前的方法一,虽然能够解决position的传递, ...

  8. Android 关于ListView中按钮监听的优化问题(方法一)

    在Android应用开发过程中经常会用到ListView,并且每次在item中都要对点击事件进行监听.在给按钮添加OnClickListener时,一般会下意识的在getView()中找到每一个But ...

  9. 如何在vue单页应用中使用百度地图

    作为一名开发人员,每次接到开发任务,我们首先应该先分析需求,然后再思考技术方案和解决方案.三思而后行,这是一个好的习惯. 需求:本项目是采用vue组件化开发的单页应用项目,现需要在项目中引入百度的地图 ...

随机推荐

  1. Mybatis 系列1

    第一篇教程, 就先简单地写个demo, 一起来认识一下mybatis吧. 为了方便,我使用了maven, 至于maven怎么使用, 我就不做介绍了.没用过maven的, 也不影响阅读. 一.Mybat ...

  2. Wooden Sticks -HZNU寒假集训

    Wooden Sticks There is a pile of n wooden sticks. The length and weight of each stick are known in a ...

  3. wxpython实现界面跳转

    wxPython实现Frame之间的跳转/更新的一种方法 wxPython是Python中重要的GUI框架,下面通过自己的方法实现模拟类似PC版微信登录,并跳转到主界面(朋友圈)的流程. (一)项目目 ...

  4. Yii2基本概念之——生命周期(LifeCycle)

    人有生老病死,一年有春夏秋冬四季演替,封建王朝有兴盛.停滞.衰亡的周期律--"其兴也勃焉,其亡也忽焉".换句话说,人,季节,王朝等等这些世间万物都有自己的生命周期.同样地,在软件行 ...

  5. leetCode刷题(使用链表做加法)

    Input: (2 -> 4 -> 3) + (5 -> 6 -> 4) Output: 7 -> 0 -> 8 Explanation: 342 + 465 = ...

  6. 使用opencv调用24*24点阵字库和8*16ASCII字库在图片显示文字数字

    课程实验:编程读汉字点阵字库,把自己的名字和学号叠加到图片的右下位置. 主要步骤分为三部分 第一部分:读取图片(文件读取) 第二部分:读取文字并从字库中提取相应的编码(字库的存储原理) 第三部分:将相 ...

  7. 单片机开发——02工欲善其事必先利其器(Proteus软件安装破解)

    在单片机开发工程中,博主经常通过模拟软件Proteus进行模拟仿真,将编译生成的"HEX"文件下载在单片机芯片中,然后进行后期的debug工作,当模拟仿真完成之后,进行硬件测试部分 ...

  8. tomcat 构建问题记录

    mvng构建程序包com.sun.image.codec.jpeg不存在------->缺少serlet的jar包 MasterSlaveRoutingDataSource不是抽象的, 并且未覆 ...

  9. python_方法说明

    方法用来描述对象所具有的行为,例如,列表对象的追加元素.插入元素.删除原宿.排序,字符串对象的分隔.连接.排版.替换.烤箱的温度设置.烘烤,等等 在类中定义的方法可以粗略分为四大类:公有方法.私有方法 ...

  10. AbstractQueuedSynchronizer原理及代码分析

    一.AQS简介 AbstractQueuedSynchronizer(AQS)是java.util.concurrent并发包下最基本的同步器,其它同步器实现,如ReentrantLock类,Reen ...