背景
在实现业务埋点过程中,页面的地址收集是非常重要的数据,为什么重要呢?可以思考一下场景:
- 当产品需要统计用户从哪个入口进入页面的,也就是用户的转化路径如何?
- 当我们进行了一个不同的转换路径A/B Test测试的时候,如何分别计算不同方案的效果?
- 有个页面的参数来自不同入口的传入,出现传入参数缺失的情况,那么我们如何排除? 以上的三种场景也是我们开发中常常遇到的问题,我们也通过相关的埋点上报当前页面地址及上个页面的地址来分析页面跳转路径,从而帮助我们排查问题及BI数据统计。
实现分析
当前我们使用的路由是两种模式:hash和history,那么我们可以通过监听及重新相关方法来获取对应的更新地址,另外还需要考虑页面通过window.open及window.location.href方式打开的情况。
缓存页面地址
首页我们需要新建一个缓存地址的数据,可以进行更新,其实一般场景下我们只需要缓存当前页面及上个页面的地址即可,所以需要封装一个固定控制数据长度类,帮助管理地址数据。
/**
* 固定长度的list, push超出长度,自动移除首个元素
*/
class FixedLengthList<T> extends Array<T> {
readonly fixedLength: number
constructor(length: number, initialItems: T[] = []) {
super()
this.fixedLength = length
this.add(...initialItems)
}
add(...items: T[]) {
const existLength = this.length
const resultLength = existLength + items.length
const shiftNumber = resultLength - this.fixedLength
if (shiftNumber > 0) {
if (shiftNumber <= existLength) {
this.splice(0, shiftNumber)
} else {
this.splice(0, existLength)
const deleteCount = items.length - (this.fixedLength)
items.splice(0, deleteCount)
}
}
this.push(...items)
return this.length
}
}
export default FixedLengthList
收集路由的变化
history模式
histroy模式基于HTML5的History API实现的,需要重写window.history.pushState和window.history.replaceState这两个API,在重写的方法里面触发路由的收集。另外调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发,比如点击后退按钮(或者在 JavaScript 中调用 history.back() 方法),同样需要监听popstate事件,代码如下:
首先自定义事件locationchange,通过该事件监听url变化
window.addEventListener('locationchange', () => {
// 获取window.locaotion.href
})
重新相关方法及增加事件监听
window.history.pushState = ((f) =>
function pushState(...rest) {
f.apply(this, rest)
window.dispatchEvent(new Event('pushstate'))
window.dispatchEvent(new Event('locationchange'))
})(window.history.pushState)
window.history.replaceState = ((f) =>
function replaceState(...rest) {
f.apply(this, rest)
window.dispatchEvent(new Event('replacestate'))
window.dispatchEvent(new Event('locationchange'))
})(window.history.replaceState)
window.addEventListener('popstate', () => {
window.dispatchEvent(new Event('locationchange'))
})
hash模式
hash模式比较监听,只需要监听hashchang事件触发我们自定义的locationchange事件即可,代码如下:
window.addEventListener('hashchange', () => {
window.dispatchEvent(new Event('locationchange'))
})
以上路由更新之后的页面地址收集已处理完毕,接下来处理页面从其他页面被直接打开的情况。
收集页面从其他页面跳转
有些业务场景需要从其他平台跳转过来,可以通过document.referer来获取上个页面的地址,当document.referrer为null的时候为页面首次打开的时候,需要加以判断,在初始化的过程中执行一次,代码如下:
if (document.referrer) {
// 添加对应的document.referrer
}
页面第一次打开
上面两种方式都没有考虑用户直接通过地址栏打开的情况,那么需要获取初始化手动收集window.location.hef。
以上我们基本处理完页面url的收集,代码看起来比较松散,需要聚合到一个类里面提供调用,那么我们就新建一个UrlListener类来进行执行,代码如下:
class UrlListener {
/** url队列 */
urlList = new FixedLengthList(2)
constructor() {
// 收集上一个页面的url地址
if (document.referrer) {
this.urlList.add(document.referrer)
}
// 收集页面初始化的地址
this.urlList.add(window.location.href)
this.urlChange()
}
/** 监听路由改变 */
urlChange = () => {
// 重写pushState, replaceState方法
window.history.pushState = ((f) =>
function pushState(...rest) {
f.apply(this, rest)
window.dispatchEvent(new Event('pushstate'))
window.dispatchEvent(new Event('locationchange'))
})(window.history.pushState)
window.history.replaceState = ((f) =>
function replaceState(...rest) {
f.apply(this, rest)
window.dispatchEvent(new Event('replacestate'))
window.dispatchEvent(new Event('locationchange'))
})(window.history.replaceState)
window.addEventListener('popstate', () => {
window.dispatchEvent(new Event('locationchange'))
})
window.addEventListener('hashchange', () => {
window.dispatchEvent(new Event('locationchange'))
})
// 监听locationchange事件
window.addEventListener('locationchange', () => {
const lastUrl = this.urlList[this.urlList.length - 1]
if(lastUrl !== document.location.href) {
this.urlList.add(document.location.href)
}
})
}
}
总结
我们来总结一下收集页面地址的有三个场景:
- 路由改变
- 通过
window.open及JS执行window.location.href场景 - 用户通过地址栏首次打开页面
通过处理以上三个场景,通过
UrlListener类实例中的urlList属性可以获取到当前页面及上个页面的地址。