前端实现埋点&监控

文章目录

    • 一、埋点&监控
    • 二、前置知识
      • 1. 区分JS模块化
      • 2. rollup
      • 3. History
        • 3.1 history.pushState()
        • 3.2 history.replaceState()
        • 3.3 popstate事件
      • 4. JS二进制
        • 4.1 Blob
        • 4.2 File
        • 4.3 FileReader
        • 4.4 ArrayBuffer
        • 4.5 Object URL
        • 4.6 Base64
        • 4.7 格式转换
      • 5. sendBeacon发送请求
    • 三、功能实现
      • 1. 安装依赖与配置打包命令
      • 2. rollup打包配置
      • 3. 类型定义
      • 4. 实现pv操作事件监听
      • 5. 写一个简单接口测验埋点结果
      • 6. 核心代码
      • 7. 校验结果
      • 参考

一、埋点&监控

实现埋点功能的意义主要体现在以下几个方面:

  1. 数据采集:埋点是数据采集领域(尤其是用户行为数据采集领域)的术语,它针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。通过埋点,可以收集到用户在应用中的所有行为数据,例如页面浏览、按钮点击、表单提交等。
  2. 数据分析:采集的数据可以帮助业务人员分析网站或者App的使用情况、用户行为习惯等,是后续建立用户画像、用户行为路径等数据产品的基础。通过数据分析,企业可以更好地了解用户需求,优化产品和服务。
  3. 改进决策:通过对埋点数据的分析,企业可以了解用户的真实需求和行为习惯,从而做出更符合市场和用户需求的决策,提高产品和服务的质量和竞争力。
  4. 优化运营:通过埋点数据,企业可以了解用户的兴趣和行为,从而更好地定位目标用户群体,优化运营策略,提高运营效率和收益。
  5. 预测趋势:通过对埋点数据的分析,企业可以预测市场和用户的未来趋势,从而提前做好准备,把握市场机遇,赢得竞争优势。

总之,实现埋点功能可以帮助企业更好地了解用户需求和行为习惯,优化产品和服务,改进决策,优化运营并预测趋势,具有重要的意义和作用。

常见的埋点包括:pv【PageView】上报(包括history上报、hash上报)、uv【UserView】上报、dom事件上报、js报错上报(包括常规错误上报、Promise报错上报)

下面我们通过使用nodejsTypeScriptrollup等技术栈实现一个简易的埋点上报的sdk,并发布npm。

通过这篇文章你可以学习到:埋点&监控、区分js模块化、打包工具rollup、API之History、JS二进制、sendBeacon发送post请求等知识。

二、前置知识

1. 区分JS模块化

因为要在nodejs环境下并使用rollup打包输出支持不同规范的模块,因此了解JS模块化相关知识是必要的。

主流模块化规范有:

  • CommonJS规范

  • AMD规范

  • CMD规范

  • ESM规范

  • UMD规范

这里建议看一下这篇博客:前端模块化详解(完整版),里面详细讲解了主流模块

下面我们对主流模块做个总结,如下:

序号 模块化规范 备注
1 CommonJS CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMDCMD解决方案
2 AMD AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。
3 CMD CMD规范整合了CommonJSAMD规范的特点, CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
4 UMD UMDAMDCommonJS两者的结合,这个模式中加入了当前存在哪种规范的判断,所以能够“通用”,它兼容了AMDCommonJS,同时还支持老式的“全局”变量规范
5 ESM ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案

2. rollup

要发布npm包,打包库是必然的,而rollupwebpack更适合打包库,因此学习rollup也是必要的。

建议看一下这篇博客:安装 Rollup 以及 Rollup 和 Webpack 的区别

这里总结一下rollupwebpack的区别:

  1. webpack 由于年代相对久远,在 commonjs 后且 esMoudles 之前,所以通过 webpack 通过自己来实现 commonjs 等语法,rollup 则可以通过配置打包成想要的语法,比如 esm
  2. 所以说 rollup 很适合打包成 ,而 webpack 比较适合用来做来打包应用
  3. 由于rollup不能够直接读取node_modules中的依赖项,需要引入加载npm模块的插件:rollup-plugin-node-resolve
  4. 由于rollup默认只支持esm模块打包,所以需要引入插件来支持cjs模块:rollup-plugin-commonjs
  5. 由于 rollup 通过可以 esm 模块开发和打包,所以支持 tree-shaking 模式
  6. vite 就是 rollup 开发而来的

3. History

实现Page View埋点往往需要使用HistoryAPI,因为它可以帮助我们更好地控制页面的状态和导航。

在SPA中,页面的状态通常由内部状态管理,而不是通过URL来表现。因此,传统的PV埋点方法(例如通过document.referrer)可能无法正确计算PV。

使用History API可以让我们更精细地控制页面的导航和状态。我们可以使用history.pushState()方法将新的状态添加到历史记录中,并更新URL,但不会触发页面刷新。这样,我们可以在用户与页面交互时跟踪其导航路径,并计算PV。

另外,当用户点击浏览器的后退按钮时,我们可以使用popstate事件来获取上一个历史记录状态,并根据需要进行处理。这可以帮助我们处理用户在SPA中的导航,并提供更准确的PV数据。

History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录,它的实例方法back()forward()go()大家应该都比较熟悉就不一一介绍了,这里主要介绍一下pushState()replaceState()以及popstate事件,注意:popstate事件并么有使用驼峰命名方式。

3.1 history.pushState()

在HTML文档中,history.pushState()方法向浏览器的会话历史栈增加了一个条目。该方法是异步的。为 popstate 事件增加监听器,以确定导航何时完成。state 参数将在其中可用。

语法:history.pushState(state, title, url)

其中,state 对象是一个 JavaScript 对象,其与通过 pushState() 创建的新历史条目相关联。每当用户导航到新的 state,都会触发 popstate 事件,并且该事件的 state 属性包含历史条目 state 对象的副本。

title标题,由于历史原因是个必填项。url可以是相对路径也可以是绝对路径,浏览器会跳转到对应页面。

对比window.loacation

从某种程度来说,调用 pushState() 类似于 window.location = "#foo"window.location.hash将变成#foohash一般情况下为url后#及其后面一部分组成,这一部分为网页的位置,也称为为锚点),它们都会在当前的文档中创建和激活一个新的历史条目。但是 pushState() 有以下优势:

  • 新的 URL 可以是任何和当前 URL 同源的 URL。然而,如果你仅修改 hash,将其设置到 window.location,将使你留在同一文档中。【pushState即使是和当前同源的url也能加一条历史到历史栈】
  • 改变页面的 URL 是可选的。相反,设置 window.location = "#foo"; 仅仅会在当前 hash 不是 #foo 情况下,创建一条新的历史条目。
  • 你可以使用你的新历史条目关联任意数据。使用基于 hash 的方式,你需要将所有相关的数据编码为一个短字符串。

更详细内容可以看MDN——pushState()方法

3.2 history.replaceState()

replaceState()方法使用state objects, title,和 URL 作为参数,修改当前历史记录实体,如果你想更新当前的 state 对象或者当前历史实体的 URL 来响应用户的的动作的话这个方法将会非常有用。

语法:history.replaceState(stateObj, title[, url])

更详细内容可以看MDN——replaceState()方法

3.3 popstate事件

每当激活同一文档中不同的历史记录条目时,popstate 事件就会在对应的 window 对象上触发。如果当前处于激活状态的历史记录条目是由 history.pushState() 方法创建的或者是由 history.replaceState() 方法修改的,则 popstate 事件的 state 属性包含了这个历史记录条目的 state 对象的一个拷贝。

调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发,比如点击后退按钮(或者在 JavaScript 中调用 history.back() 方法)。即,在同一文档的两个历史记录条目之间导航会触发该事件。

更详细内容可以看MDN——popstate事件

4. JS二进制

JavaScript 提供了一些 API 来处理文件或原始文件数据,例如:FileBlobFileReaderArrayBufferbase64 等。它们之间的关系如下:

前端实现埋点&监控_第1张图片

以下是我对谈谈JS二进制:File、Blob、FileReader、ArrayBuffer、Base64这篇文章的一点小结,详细内容请看原文,建议把原文的代码都敲一遍

4.1 Blob

Blob(binary large object),即二进制大对象,它是 JavaScript 中的一个对象,表示原始的类似文件的数据。

Blob对象是一个只读不可修改的二进制文件,它的数据可以按文本或二进制的格式进行读取。

创建Blob对象

new Blob(array, option)

其中

  • array:由 ArrayBufferArrayBufferViewBlobDOMString 等对象构成的,将会被放进 Blob
  • options:可选的 BlobPropertyBag 字典,它可能会指定如下两个属性。
    • type:默认值为 “”,表示放入到blob中的数组内容的MIME类型。
    • endings:默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入,不常用。

常见MIME类型如下:

前端实现埋点&监控_第2张图片

示例:以下实现了实例化了一个Blob对象并使用URL.createObjectUrl()方法将其转化为一个URL。

前端实现埋点&监控_第3张图片

点击这个url可以看到如下结果:

前端实现埋点&监控_第4张图片

4.2 File

文件(File)接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。实际上,File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。Blob 的属性和方法都可以用于 File 对象。

注意:File 对象中只存在于浏览器环境中,在 Node.js 环境中不存在。

在 JavaScript 中,主要有两种方法来获取 File 对象:

  • 元素上选择文件后返回的 FileList 对象
  • 文件拖放操作生成的 DataTransfer 对象;

下面实现一下拖放文件生成dataTransfer对象并输出结果:

<div style="width: 500px;height: 500px;background-color: gray;" id="dorp-zone"></div>
<script>
    const dorpZone = document.getElementById('dorp-zone');
    dorpZone.addEventListener('drop', (e) => {
        // 阻止默认事件 如放置文件将显示在浏览器新建窗口中
        e.preventDefault();
        console.log(e);
        const file = e.dataTransfer.files[0];
        console.log(file);
    })
    dorpZone.addEventListener('dragover', (e) => {
        e.preventDefault();
    })
</script>

结果如下:

前端实现埋点&监控_第5张图片

4.3 FileReader

FileReader 是一个异步 API,用于读取文件并提取其内容以供进一步使用。FileReader 可以将 Blob 读取为不同的格式。

注意:FileReader 仅用于以安全的方式从用户(远程)系统读取文件内容,不能用于从文件系统中按路径名简单地读取文件

创建FIleReader对象

const reader = new FileReader()

FileReader对象常用的属性如下:

  • error:表示在读取文件时发生的错误

  • result:文件内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。

  • readyState:表示FileReader状态的数字。取值如下:

    常量名 描述
    EMPTY 0 还没有加载任何数据
    LOADING 1 数据正在被加载
    DONE 2 已完成全部的读取请求

FileReader对象提供了以下方法来加载文件:

  • readAsArrayBuffer():读取指定 Blob 中的内容,完成之后,result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象
  • readAsBinaryString():读取指定 Blob 中的内容,完成之后,result 属性中将包含所读取文件的原始二进制数据
  • readAsDataURL():读取指定 Blob 中的内容,完成之后,result 属性中将包含一个data: URL 格式的 Base64 字符串以表示所读取文件的内容
  • readAsText():读取指定 Blob 中的内容,完成之后,result 属性中将包含一个字符串以表示所读取的文件内容

FileReader对象常用事件如下:

  • abort:该事件在读取操作被中断时触发
  • error:该事件在读取操作发生错误时触发
  • load:该事件在读取操作完成时触发
  • progress:该事件在读取 Blob 时触发

示例:以下实现了上传图片并转成base64的url以展示在页面

<input type="file" id="fileInput">
<img id="img" src="" alt="">
<script>
    const fileInput = document.getElementById('fileInput');
    const img = document.getElementById('img');
    const reader = new FileReader();
    fileInput.addEventListener('change', (e) => {
        console.log(e);
        const file = e.target.files[0];
        // reader.readAsText(file);  // 把图片文件转成字符串会是一大堆乱码
        reader.readAsDataURL(file);  // 转成base64的url
        reader.onload = (e) => {
            img.src = e.target.result;
            console.log(e.target.result);
        }
    })
</script>

结果如下:

前端实现埋点&监控_第6张图片

4.4 ArrayBuffer

ArrayBuffer对象用来表示通用的、固定长度的原始二进制数据缓冲区ArrayBuffer 的内容不能直接操作,只能通过 DataView 对象或 TypedArrray 对象来访问。这些对象用于读取和写入缓冲区内容。

ArrayBuffer 本身就是一个黑盒,不能直接读写所存储的数据,需要借助以下视图对象来读写:

  • TypedArray:用来生成内存的视图,通过9个构造函数,可以生成9种数据格式的视图。
  • DataViews:用来生成内存的视图,可以自定义格式和字节序。
4.5 Object URL

Object URL(MDN定义名称)又称Blob URL(W3C定义名称),是HTML5中的新标准。它是一个用来表示File ObjectBlob Object 的URL。在网页中,我们可能会看到过这种形式的 Blob URL

blob:https://zhuanlan.zhihu.com/47cca259-d9cd-41dc-b2a9-319c1db26f32

其实 Blob URL/Object URL 是一种伪协议,允许将 BlobFile 对象用作图像、二进制数据下载链接等的 URL 源

对于 Blob/File 对象,可以使用 URL构造函数的 createObjectURL() 方法创建将给出的对象的 URL。这个 URL 对象表示指定的 File 对象或 Blob 对象。我们可以在

你可能感兴趣的:(项目工程化,Node.js,JavaScript,前端,埋点,javascript,node.js,node,开发,typescript)