vue项目前端埋点的实现


Posted in Javascript onMarch 06, 2019

埋点方案的确定。业界的埋点方案主要分为以下三类:

  • 代码埋点:在需要埋点的节点调用接口,携带数据上传。如百度统计等;
  • 可视化埋点:使用可视化工具进行配置化的埋点,即所谓的「无痕埋点」,前端在页面加载时,可以读取配置数据,自动调用接口进行埋点。如开源的Mixpanel;
  • 无埋点:前端自动采集全部事件并上报埋点数据。如国内的神策数据等;

在当时排期紧凑,人力紧缺的情况下,显然不允许我们去开发可视化埋点方案和无埋点方案,所以只能采取代码埋点方案。

命令式埋点

命令式埋点,顾名思义,开发者需要手动在需要埋点的节点处进行埋点。如点击按钮或链接后的回调函数、页面ready时进行请求的发送。大家肯定都很熟悉这样的代码:

// 页面加载时发送埋点请求
$(document).ready(function(){
  // ... 这里存在一些业务逻辑
  sendRequest(params);
});
// 按钮点击时发送埋点请求
$('button').click(function(){
  // ... 这里存在一些业务逻辑
  sendRequest(params);
});

可以很容易发现,这样的做法很有可能会将埋点代码侵入业务代码,这使整体业务代码变得繁琐,容易出错,且后续代码会愈加膨胀,难以维护。所以,我们需要让埋点的代码与具体的业务逻辑解耦,即 声明式埋点 ,从而提高埋点的效率和代码的可维护性。

声明式埋点

理论上,声明式埋点只需要关注两个问题:

  • 需要埋点的DOM节点;
  • 所需携带的数据

因此,可以很快想出一个声明式埋点的方法:

// key表示埋点的唯一标识;act表示埋点方式
<button data-stat="{key:'111', act: 'click'}">埋点</button>

那么可以去遍历DOM树,找到 [data-stat] 的节点,给这个button绑上click事件,把这些参数在回调函数中通过请求发出去。

在DOM节点(html)上声明埋点,与业务逻辑(通常在Javascript文件中)就解耦了。调用也很方便。

看起来很美,但这样就能解决问题了吗?显然是不够的。还需要解决以下问题:

  • 遍历DOM树的时机问题,一个简单的例子,一个表格的行数据是通过异步加载,而表格行中的操作按钮需要埋点,那么在DOM ready的时候去遍历,显然是无法找到的
  • 绑定埋点事件次数的问题,怎样保证埋点事件不会被重复绑定到元素上,一次操作发了N个埋点请求?
  • 如何处理特有的埋点行为,如页面展现埋点,区域展现埋点?
  • 如何在解绑时,销毁已绑定的事件?

1.自定义指令实现埋点数据统计

在项目中通常需要做数据埋点,这个时候,使用自定义指令将会变非常简单

在项目入口文件 main.js 中配置我们的自定义指令

// 坑位埋点指令
Vue.directive('stat', {
 bind(el, binding) {
  el.addEventListener('click', () => {
   const data = binding.value;
   let prefix = 'store';
   if (OS.isAndroid || OS.isPhone) {
    prefix = 'mall';
   }
   analytics.request({
    ty: `${prefix}_${data.type}`,
    dc: data.desc || ''
   }, 'n');
  }, false);
 }
});

2.使用路由拦截统计页面级别的 PV

由于第一次在单页应用中尝试数据埋点,在项目上线一个星期之后,数据统计后台发现,首页的 PV 远远高于其它页面,数据很不正常。后来跟数据后台的人沟通询问他们的埋点统计原理之后,才发现其中的问题所在。

传统应用,一般都在页面加载的时候,会有一个异步的 js 加载,就像百度的统计代码类似,所以我们每个页面的加载的时候,都会统计到数据;然而在单页应用,页面加载初始化只有一次,所以其它页面的统计数据需要我们自己手动上报

解决方案

使用 vue-router 的 beforeEach 或者 afterEach 钩子上报数据,具体使用哪个最好是根据业务逻辑来选择。

const analyticsRequest = (to, from) => {
 // 只统计页面跳转数据,不统计当前页 query 不同的数据
 // 所以这里只使用了 path, 如果需要统计 query 的,可以使用 to.fullPath
 if (to.path !== from.path) {
  analytics.request({
   url: `${location.protocol}//${location.host}${to.path}`
  });
 }
};

router.beforeEach((to, from, next) => {
 if (to.matched.some(record => record.meta.requiresAuth)) {
  // 这里做登录等前置逻辑判断
  // 判断通过之后,再上报数据
  ...
  analyticsRequest(to, from);
 } else {
  // 不需要判断的,直接上报数据
  analyticsRequest(to, from);
  next();
 }
});

在组件中使用我们的自定义指令

vue项目前端埋点的实现

基于 jquery + widget 的老项目,

那么在这些项目中的DOM操作是jquery甚至原生DOM API来实现,Vue的自定义指令就无法工作

基于MutationObserver API的Mixin

MutationObserver是在DOM3标准中提出的标准API,提供让开发者感知到在某一个DOM节点变更的能力。可以监听以下场景:

  • childList: 目标节点的子节点插入删除引起的变更
  • attributes: 目标节点属性改变引起的变更
  • characterData: 目标节点的文本节点改变引起的变更,如通过appendData()等
  • subtree: 目标节点的子孙节点改变引起的变更
  • attributeOldValue:当attribute监听被设定为true时,可以记录改变前的属性值
  • characterDataOldValue:当characterData监听被设定为true时,可以记录改变前的属性值
  • attributeFilter:可以设定需要监听的属性列表

但为了保证MutationObserver可以在所有浏览器上正常工作,我们仍然引入了这个API的polyfill,详情可见这里。

在此能力的前提下,我们就可以在任意的DOM操作下触发Vue进行重新解析指令。

我们将 MutationObserver 封装进一个 Vue mixin , 非Vue应用的业务代码只需要引入这个mixin,这样也可以很好地解耦。
详细的实现原理可以见以下伪代码:

let observer;
export default {
 ready() {
  // 开启监听
  observer = new MutationObserver(mutations => {
   this.$compile(this.$el);
  });
  observer.observe(this.$el, config);
 },
 destroyed() {
  // 清理工作
  observer.disconnect();
  observer.takeRecords();
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript 变量作用域 两个可能会被忽略的小特性
Mar 23 Javascript
在浏览器中获取当前执行的脚本文件名的代码
Jul 19 Javascript
基于PHP和Mysql相结合使用jqGrid读取数据并显示
Dec 02 Javascript
Easyui和zTree两种方式分别实现树形下拉框
Aug 04 Javascript
jQuery选择器之子元素选择器详解
Sep 18 jQuery
JS写XSS cookie stealer来窃取密码的步骤详解
Nov 20 Javascript
详解webpack + react + react-router 如何实现懒加载
Nov 20 Javascript
js 数组详细操作方法及解析合集
Jun 01 Javascript
微信小程序实现自上而下字幕滚动
Jul 14 Javascript
vue3.0 CLI - 2.1 -  component 组件入门教程
Sep 14 Javascript
vue响应式系统之observe、watcher、dep的源码解析
Apr 09 Javascript
vue 解决遍历对象显示的顺序不对问题
Nov 07 Javascript
vue2.0结合Element-ui实战案例
Mar 06 #Javascript
JS使用JSON.parse(),JSON.stringify()实现对对象的深拷贝功能分析
Mar 06 #Javascript
jQuery使用$.extend(true,object1, object2);实现深拷贝对象的方法分析
Mar 06 #jQuery
微信小程序利用swiper+css实现购物车商品删除功能
Mar 06 #Javascript
JS实现数组深拷贝的方法分析
Mar 06 #Javascript
node.js中ws模块创建服务端和客户端,网页WebSocket客户端
Mar 06 #Javascript
node.js中express模块创建服务器和http模块客户端发请求
Mar 06 #Javascript
You might like
php 上传文件类型判断函数(避免上传漏洞 )
2010/06/08 PHP
rrmdir php中递归删除目录及目录下的文件
2011/05/15 PHP
php获取文件名后缀常用方法小结
2015/02/24 PHP
PHP编程实现脚本异步执行的方法
2017/08/09 PHP
支持汉转拼和拼音分词的PHP中文工具类ChineseUtil
2018/02/23 PHP
PHP实现15位身份证号转18位的方法分析
2019/10/16 PHP
[对联广告] JS脚本类
2006/08/27 Javascript
Javascript 写的简单进度条控件
2008/01/22 Javascript
Jquery 获得服务器控件值的方法小结
2010/05/11 Javascript
jquery ajax例子返回值详解
2012/09/11 Javascript
js网页中的(运行代码)功能实现思路
2013/02/04 Javascript
jquery实现人性化的有选择性禁用鼠标右键
2014/06/30 Javascript
JavaScript里四舍五入函数round用法实例
2015/04/06 Javascript
jQuery EasyUI window窗口使用实例代码
2017/12/25 jQuery
jQuery实现轮播图源码
2019/10/23 jQuery
Element Dropdown下拉菜单的使用方法
2020/07/26 Javascript
[00:32]2016完美“圣”典风云人物:Maybe宣传片
2016/12/05 DOTA
python实现的简单猜数字游戏
2015/04/04 Python
python3解析库lxml的安装与基本使用
2018/06/27 Python
python 对txt中每行内容进行批量替换的方法
2018/07/11 Python
python+opencv+caffe+摄像头做目标检测的实例代码
2018/08/03 Python
Python实现EXCEL表格的排序功能示例
2019/06/25 Python
Python实现的对一个数进行因式分解操作示例
2019/06/27 Python
python画蝴蝶曲线图的实例
2019/11/21 Python
Win 10下Anaconda虚拟环境的教程
2020/05/18 Python
Python计算信息熵实例
2020/06/18 Python
纯CSS3制作的鼠标悬停时边框旋转
2017/01/03 HTML / CSS
html5图片上传预览示例分享
2014/04/14 HTML / CSS
制衣厂各岗位职责
2013/12/02 职场文书
党的群众路线教育实践活动宣传方案
2014/02/23 职场文书
三问三解心得体会
2014/09/05 职场文书
党的群众路线教育实践活动个人对照检查剖析材料
2014/09/23 职场文书
开票员岗位职责
2015/02/12 职场文书
2015年推广普通话演讲稿
2015/03/20 职场文书
Python中的np.argmin()和np.argmax()函数用法
2021/06/02 Python
讲解Python实例练习逆序输出字符串
2022/05/06 Python