介绍
应用中特定的流程收集一些信息,用来跟踪应用使用的状况,以便后续进一步优化产品或者提供运营的数据支撑。如访问数,访客,停留时常,页面浏览数等
数据收集类型
- 
事件数据收集 页面访问量收集 使用监听url变化, 如果是history,则可以监听pushstate 和replacestate 
 hashchange 页面停留时间收集
 页面停留时间,就是记录从访问页面到离开/关闭页面的时间。不管是SPA应用还是非SPA应用,都是记录从页面加载到页面关闭或跳转的时间,
 load => 单页面应用 replaceState, pushState, 非单页面应用popstate代码如下: // 首次进入页面 window.addEventListener("load", () => { // 记录时间 const time = new Date().getTime(); dulation.startTime = time; }); // 单页应用页面跳转(触发 replaceState) window.addEventListener("replaceState", () => { const time = new Date().getTime(); dulation.value = time - dulation.startTime; // 上报 // ..... // 上报后重新初始化 dulation, 以便后续跳转计算 dulation.startTime = time; dulation.value = 0; }); // 单页应用页面跳转(触发 pushState) window.addEventListener("pushState", () => { const time = new Date().getTime(); dulation.value = time - dulation.startTime; // 上报 // ..... // 上报后重新初始化 dulation, 以便后续跳转计算 dulation.startTime = time; dulation.value = 0; }); // 非单页应用跳转触发 popstate window.addEventListener("popstate", () => { const time = new Date().getTime(); dulation.value = time - dulation.startTime; // 上报 // ..... // 上报后重新初始化 dulation, 以便后续跳转计算 dulation.startTime = time; dulation.value = 0; }); // 页面没有任何跳转, 直接关闭页面的情况 window.addEventListener("beforeunload", () => { const time = new Date().getTime(); dulation.value = time - dulation.startTime; // 上报 // ..... // 上报后重新初始化 dulation, 以便后续跳转计算 dulation.startTime = time; dulation.value = 0; });``` ## 异常数据收集 ```window.addEventListener("error", (e) => { console.log("上报", "error"); }); window.addEventListener("unhandledrejection", (e) => { console.log("上报", "unhandledrejection"); }); ```
数据上报
- 数据格式
{
  uid; // 用户id
  title; // 页面标题
  url; // 页面的url
  time; // 触发埋点的时间
  ua; // userAgent
  screen; // 屏幕信息, 如 1920x1080
  type // 数据类型,根据触发的不同埋点有不同的类型
  data; // 根据不同的type,此处的数据也不同,如 事件数据有元素名称,事件名称、页面停留时间有停留时长....
  sdk // sdk 相关信息
}
- 上报方式
 一般情下使用
 XMLHttpReuqest(axios)或者Fetch,但用其有两个缺陷,一是有跨域问题,二是在页面关闭时会中断正在发送的数据。
 考虑到以上几点,一般使用图片来发送数据或者使用navigator.sendBeacon,这里就使用sendBeacon,简单方便。也可以两者结合(如果浏览器不支持sendBeancon,就使用图片的方式)。
function send(data) {
  const url = `xxxx`
  if (navigator.sendBeacon) {
    navigator.sendBeacon(url, JSON.stringify(data));
  } else {
    const imgReq = new Image();
    imgReq.src = `${url}?params=${JSON.stringify(
      data
    )}&t=${new Date().getTime()}`;
  }
}
sdk实现
// JS 完整代码部分
(function (e) {
  function wrap(event) {
    const fun = history[event];
    return function () {
      const res = fun.apply(this, arguments);
      const e = new Event(event);
      window.dispatchEvent(e);
      return res;
    };
  }
  class TrackingDemo {
    constructor(options = {}) {
      // 重写 pushState、replaceState
      window.history.pushState = wrap("pushState");
      window.history.replaceState = wrap("replaceState");
      // 上报地址
      this.reportUrl = options.reportUrl || "";
      this.sdkVersion = "1.0.0";
      this._eventList = ["click", "dblclick", "mouseout", "mouseover"];
      this._dulation = {
        startTime: 0,
        value: 0,
      };
      this._initJSError();
      // 初始化事件数据收集
      this._initEventHandler();
      // 初始化PV统计
      this._initPV();
      this._initPageDulation();
    }
    setUserId(uid) {
      this.uid = uid;
    }
    _initEventHandler() {
      this._eventList.forEach((event) => {
        window.addEventListener(event, (e) => {
          const target = e.target;
          const reportKey = target.getAttribute("report-key");
          if (reportKey) {
            this._report("event", {
              tagName: e.target.nodeName,
              tagText: e.target.innerText,
              event,
            });
          }
        });
      });
    }
    _initPV() {
      window.addEventListener("pushState", (e) => {
        this._report("pv", {
          type: "pushState",
          referrer: document.referrer,
        });
      });
      window.addEventListener("replaceState", (e) => {
        this._report("pv", {
          type: "replaceState",
          referrer: document.referrer,
        });
      });
      window.addEventListener("hashchange", () => {
        this._report("pv", {
          type: "hashchange",
          referrer: document.referrer,
        });
      });
    }
    _initPageDulation() {
      let self = this;
      function initDulation() {
        const time = new Date().getTime();
        self._dulation.value = time - self._dulation.startTime;
        self._report("dulation", {
          ...self._dulation,
        });
        self._dulation.startTime = time;
        self._dulation.value = 0;
      }
      // 首次进入页面
      window.addEventListener("load", () => {
        // 记录时间
        const time = new Date().getTime();
        this._dulation.startTime = time;
      });
      // 单页应用页面跳转(触发 replaceState)
      window.addEventListener("replaceState", () => {
        initDulation();
      });
      // 单页应用页面跳转(触发 pushState)
      window.addEventListener("pushState", () => {
        initDulation();
      });
      // 非单页应用跳转触发 popstate
      window.addEventListener("popstate", () => {
        initDulation();
      });
      // 页面没有任何跳转, 直接关闭页面的情况
      window.addEventListener("beforeunload", () => {
        initDulation();
      });
    }
    _initJSError() {
      window.addEventListener("error", (e) => {
        this._report("error", {
          message: e.message,
        });
      });
      window.addEventListener("unhandledrejection", (e) => {
        this._report("error", {
          message: e.reason,
        });
      });
    }
    // 用户可主动上报
    reportTracker(data) {
      this._report("custom", data);
    }
    _getPageInfo() {
      const { width, height } = window.screen;
      const { userAgent } = navigator;
      return {
        uid: this.uid,
        title: document.title,
        url: window.location.href,
        time: new Date().getTime(),
        ua: userAgent,
        screen: `${width}x${height}`,
      };
    }
    _report(type, data) {
      const reportData = {
        ...this._getPageInfo(),
        type,
        data,
        sdk: this.sdkVersion,
      };
      if (navigator.sendBeacon) {
        navigator.sendBeacon(this.reportUrl, JSON.stringify(reportData));
      } else {
        const imgReq = new Image();
        imgReq.src = `${this.reportUrl}?params=${JSON.stringify(
          reportData
        )}&t=${new Date().getTime()}`;
      }
    }
  }
  e.TrackingDemo = TrackingDemo;
})(window);
// HTML 代码部分
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button report-key="button">按钮</button>
</body>
<script src="./tackerDemo.js"></script>
<script>
    const trackingDemo = new TrackingDemo()
</script>
</html>
工程化
参考文档