IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

页面跳转时,统计数据丢失问题探讨

小胡子哥的个人网站 2016-03-02 00:09:24 累计浏览 2,381 次
本机暂存

为了更好地了解用户对产品的使用情况,业务中,我们经常会收到埋点统计的需求,比如:

  1. 收集一段时间内用户光标在页面中的运动情况,包括光标移动、点击等行为

  2. 统计用户滚屏行为

  3. 统计用户在站点的停留时长

  4. 收集页面链接的点击数量等

无论是移动端还是 PC 端,相信很多朋友都遇到了这么几个十分让人头疼的问题:

  1. 统计某个链接的点击量,但是这个链接点击后直接跳转走了

  2. 统计页面时长问题,unload 的时候发送的统计丢失了

  3. 统计脚本还没有初始化,用户不感兴趣已经走人了等

如果我们把这样的数据交给了产品同学,可能会让他们对用户行为产生错误的认知,一定程度上影响产品的下一步改善。

传统解决方案

上面提到的问题,从技术角度可以归纳为两点:

  1. 用户关闭页面过早,统计脚本还未加载/初始化完成

  2. 用户关闭或者跳出页面的时候,请求未发出

针对第一点,概率较小,一般的处理方式就是,不要把统计脚本参合到其他脚本中,单独加载,并且放在前头,让它优先加载。很多公司的做法是,不让开发者关心统计脚本的加载,用户请求页面的时候,Nginx 会在 Body 开始标签位置注入一段脚本。

对于问题二,处理方案就有很多了。

1. 阻塞式的 Ajax 请求

还记得 XMLHttpRequest::open 方法的第三个参数吧,如果设置为 false 就是同步加载,

window.addEventListener('unload', function(event) {

 var xhr = new XMLHttpRequest(),

 xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");

 xhr.open('post', '/log', false); // 同步请求

 xhr.send(data);

});

阻塞页面关闭,当然可以在 readState 为 2 的时候就 abort 请求,因为我们不关心响应的内容,只要请求发出去就行了。

2. 暴力的死循环

原理跟上面类似,只不过是使用一个空的死循环阻塞页面关闭,

window.addEventListener('unload', function(event) {

 send(data);

 var now = +new Date;

 while(new Date - now >= 10) {} // 阻塞 10ms

});

3. 发一个图片请求阻塞

大部分浏览器都会等待图片的加载,趁这个机会把统计数据发送出去

window.addEventListener('unload', function(event) {

 send(data);

 (new Image).src = 'http://example.com/s.gif';

});

以上提到的几个方案都是一个原理,让浏览器继续保持阻塞状态,等数据发送出去后再跳转,这里存在的问题是:

  1. 少量浏览器下可能不奏效

  2. 等待一会儿再跳转,用户体验上打了折扣,尤其是移动端上

是否有更好的方案解决这个问题呢,前端同学秉着「小强精神」也提出了两个可实践的方案。

优化方案

不就是埋点统计数据嘛,非得在当前页面发送出去?优化方案的思路具有一定的跳跃性,我们考虑将数据在下跳页中发送,那么问题就转换为,如何将数据传递给下跳页?

对于链接点击量的统计,我们可以将链接信息通过 url 传递给下跳页,传递思路如下:

1. url 传参

通过数组标识一个链接的位置信息,如 [站点id,页面id,模块id,链接index],通过四个参数可以惟一标识链接位置属性,使用 URL param 参数将数组数据传递给下跳页,等待由下跳页将数据发送出去。

这里存在的问题是,下跳页中必须部署同样的统计脚本,但对一个系统来说,这是很容易做到的。我们也不会在自己的网页上放其他网站的链接吧,所以整个数据的统计都在一个闭环内。

2. 通过 window.name 传递数据

window.name 是浏览器给我们开放的一个接口,设置该属性的值后,即便页面发生了跳转,这个值依然不会变化,并且可以跨域使用。

这里存在的问题是,该属性可能被开发者用于其他途径。我们可以限制开发者直接使用 window.name,封装接口,通过接口调用,如 aralejs 提供的 nameStorage,

nameStorage.setItem(key, value);

nameStorage.getItem(key);

nameStorage.removeItem(key);

储存形式为:

scheme                  nameStorage datas

 |                            |

------------           ------------------------

nameStorage:origin-name?key1=value1&key2=value2

-----------

|

window origin name

以上虽然基本解决了数据丢失和体验差的问题,但是这也很大程度依赖于开发者的编程习惯,如不能随便玩耍 window.name;也对系统有一定的要求,必须在所有页面上部署同样的埋点脚本。

这件事情应该交给浏览器来解决

上面提到的各种方案,不乏黑科技,然而存在的问题还是一大堆,如果团队的开发者执行力不够,中途容易出现各种麻烦。所以真正能够解决这个问题的,必然还是浏览器本身!

为什么不能给用户提供这样一个 API,即使页面跳转了,也能够将上个页面的请求发出去呢?庆幸的是,W3C 工作组也想到了这个问题,提出了 Beacon API 的 草案。

Beacon API 允许开发者发送少量错误分析和上报的信息,它的特点很明显:

  1. 在空闲的时候异步发送统计,不影响页面诸如 JS、CSS Animation 等执行

  2. 即使页面在 unload 状态下,也会异步发送统计,不影响页面过渡/跳转到下跳页

  3. 能够被客户端优化发送,尤其在 Mobile 环境下,可以将 Beacon 请求合并到其他请求上,一同处理

sendBeacon 函数挂在在 navigator 上,在 unload 之前,这个函数一定是被初始化了的。其使用方式为:

window.addEventListener('unload', function(event) {

 navigator.sendBeacon('/collector', data);

});

navigator.sendBeacon(url, data);,第一个参数为数据上报的地址,第二个参数为要发送的数据,支持的数据格式有:ArrayBufferView, Blob, DOMString, 和 FormData。

Beacon 的还有一个非常实用的移动端使用场景,当用户从浏览器切换到其他 app 界面或者 Home 屏的时候,部分浏览器默认会停止页面脚本的执行,如果在这个时候使用了 unload 时间,可能会让你失望,因为 unload 事件并不会触发,此时,Beacon 就派上用途了,它是不会受影响的。

最后

本文是对页面打点丢失问题的简单探讨,枚举了我们通常会用到的一些解决方案,可能不是很完善,如果你有更好的建议,可以提出来。

很多问题,我们绞尽脑汁,可能很少会考虑,这个问题是不是应该有我们来解决,或者说这个问题交给谁处理是最恰当的。本文的探讨可以看到,浏览器本身才是最好的问题解决方,当网站流量变大之后,上面提到的丢失问题就更加明显,这也迫使浏览器本身做了改善,自然也在情理之中。

同分类推荐文章

  1. translateZ() (2026-06-25 21:18:56)
  2. translateY() (2026-06-25 21:17:56)
  3. translateX() (2026-06-25 21:16:01)

查看更多 前端 文章 →

建议继续学习

  1. 前端开发中Cookie那些事儿 (累计阅读 7,368)
  2. 点燃绳子究竟还能测出哪些时间? (累计阅读 4,188)
  3. 用户界面设计中“状态”和“动作”的表达 (累计阅读 2,933)
  4. 本地存储的兼容解决方案 (累计阅读 2,428)
  5. AJAX页面状态保持 (累计阅读 2,046)
  6. 禁用状态二三事 (累计阅读 1,867)
  7. ReactJS组件间沟通的一些方法 (累计阅读 1,831)
  8. 前端用户模块 (累计阅读 1,810)
  9. 谈谈取消键与撤消键的使用 (累计阅读 1,778)
  10. 浅谈 GUI 应用开发 (累计阅读 49)