前端统计如何实现数据传递

百度,google,友盟等的统计都用的是 <img src="..."> 的方式来统计信息,

前端用GIF打点

why,从原理说起

前端监控原理

由Web页面将用户信息(UA/鼠标点击位置/页面报错/停留时长/etc)等 上报给服务器

上报数据url_encode(百度统计/CNZZ)或JSON编码(神策/诸葛io)为字符串,通过url参数传递给后台服务器

关键在于两点

  1. 收集用户信息
  2. 上报数据,只要能上报,其实选择有多种,如下图

20190810002049.png

因此需要一种方式,尽可能的满足一下条件

  1. 不关注返回结构
  2. 传递信息尽可能小
  3. 不能阻塞web的正常业务,且影响最小

用排除法来看(大家都用git是有原因的)

不用GET/POST/HEAD请求上报?

请求都会涉及跨域。而跨域请求很容易出现由于配置不当,被浏览器拦截并报错。所以排除,且对于后端而言开放所有的跨域拦截是有安全风险的,并且由于要做跨域校验,是有性能消耗的。

为什么不用其他文件(js/css/ttf)

js等的加载只能插入到html文档内部,才能被加载,这个会阻塞浏览器的页面渲染,且改变dom结构,即使之删除了,操作dom也是相当消耗浏览器性能的,影响用户体验。所以排除。

所以选择图片

图片不仅不用插入DOM元素内,且在JS中直接new一个Image就能发起请求。而且还没有阻塞问题。就是没有js的环境下也能通过直接加载img标签来正常打点。这是其他资源所做不到的。

为什么选择GIF

原因: GIF是传输最小的合法图片格式(详情参考:图片类型解析)。

总结:用GIF的原因

  1. 没有跨域问题
  2. 不会阻塞页面加载,影响用户体验
  3. 在所有的图片中提及最小,比较BMP/PNG,可以节省41%/35%的网络资源

以为一切完了吗?没有好戏才真正开始,上代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <img src="http://0.0.0.0:8000/logo.png?from=0" id="image0" />
    <img src="http://0.0.0.0:8000/logo.png?from=1" id="image1" />
  </body>
  <script>
    var unique = (function() {
      var time = new Date().getTime() + '-',
        i = 0;
      return function() {
        return time + i++;
      };
    })();

    window.addEventListener('beforeunload', function(e) {
      var data = window['imgLogData'] || (window['imgLogData'] = {});

      var uid = unique();
      image0.src = 'http://0.0.0.0:8000/logo.png' + '?from=2_uid=' + uid;
      data[uid] = image0;
      image0.onload = image0.onerror = function() {
        image0.onload = image0.onerror = null;
        image0 = null;
        delete data[uid];
      };

      var uid = unique();
      image1.src = 'http://0.0.0.0:8000/logo.png' + '?from=3_uid=' + uid;
      data[uid] = image1;
      image1.onload = image1.onerror = function() {
        image1.onload = image1.onerror = null;
        image1 = null;
        delete data[uid];
      };

      xhr2 = new XMLHttpRequest();
      xhr2.open(
        'GET',
        'http://0.0.0.0:8000/logo.png' + '?from=4&_uid=' + unique(),
        true
      );
      xhr2.send(null);
      xhr3 = new XMLHttpRequest();
      xhr3.open(
        'GET',
        'http://0.0.0.0:8000/logo.png' + '?from=5&_uid=' + unique(),
        true
      );
      xhr3.send(null);
      xhr4 = new XMLHttpRequest();
      xhr4.open(
        'GET',
        'http://0.0.0.0:8000/logo.png' + '?from=6&_uid=' + unique(),
        true
      );
      xhr4.send(null);
      return ((e | window.event).returnValue = '123');
    });
    window.addEventListener('unload', function(e) {
      console.dir(123);
    });
  </script>
</html>v

这个代码,就是检测统计时在刷新时的图片加载顺序情况。如图(注:logo是随便的一个普通图片,可以替换成其他的,加uid是为了每次有用新的图片)

chrome 测一下

20190810004819.png

form: 4->5->6->2->3->0->1
发现什么了吗?xhr竟然比js内src要先发出(实效性很小,目前没搞清楚为什么,可能和浏览器事件执行有关系,xhr的队列比js内src请求队列,要有先执行,dom内src请求队列最后执行),而dom内src最后才发出。如果不考虑安全,跨域因素,真的用ajax要更好,而且ajax后端能返回204状态码,没有返回内容,这更好。

safari 测一下

20190810013434.png

form: 2->3->4->5->6->0->1
调整xhr的调用顺序

20190810014238.png
form: 4->2->3->5->6->0->1
从图中看出safari对xhr和js内部src没有做区分,但是报错了,浏览器任务不安全,应为request的请求头中没有指名服务器信息,和请求格式等。由安全性问题,所以选择js内src最好,