神策的基本事件模型包括事件(Event)和用户(User)两个;比如说要统计今天注册了tcl会员小程序的人数。区分是注册事件还是别的事件,用到了事件的模型。每个用户启动N次只能算一次,用到了用户的模型。
埋点代码的基本思路
埋点代码的基本思路和‘追踪某个用户的某个行为’这件事的描述是一样的。首先建立一个人物模型,然后追踪这个人物的行为。这个先后顺序在代码中的体现:
// 通过id标识建立一个用户模型
sensors.login(id)
// 给这个模型完善描述信息
setProfile({
sex: '男',
city: '深圳',
})
// 用户触发了注册动作时这里会执行
register() {
sensors.track('register', {
title: '首页',
share_mobile: 13590035000,
time: '2020-03-13 16:00:00'
})
}
复制代码
前端埋点代码的设计案例
接到一个需求,所有的页面都要采集页面的切换动作,比如当前页面停留的时间,下个页面的路径,标题。重点是所有页面
痛点和难点
写代码之前,先思考下蒙头写代码会带来哪些痛点。而认识并且规避掉这些痛点,就是接下来的要做的事情和思路
1: 埋点代码不是主流的业务逻辑,如果和业务逻辑糅合在一起,会非常的乱,长期的需求迭代和多人协作会导致维护代码的时候非常困难
2: 涉及的所有的页面,每个页面都要写相应动作的埋点代码,有N个页面就会在N个页面写一遍,迭代修改的时候同一个埋点的需求也要改N遍
3: 埋点的动作次数很多,代码量会很大
4: 涉及的所有的页面,如果都在每个页面上写,容易漏写
而这些问题,神策源码其实也遇到过,所以可以参考下神策源码的思路,解决这些痛点。
神策源码是怎么解决的
看了半天神策源码,终于发现了关键所在:
var oldApp = App;
App = function(option) {
mp_proxy(option, "onLaunch", 'appLaunch');
mp_proxy(option, "onShow", 'appShow');
mp_proxy(option, "onHide", 'appHide');
oldApp.apply(this, arguments);
};
var oldPage = Page;
Page = function(option) {
mp_proxy(option, "onLoad", 'pageLoad');
mp_proxy(option, "onShow", 'pageShow');
if (typeof option.onShareAppMessage === 'function') {
sa.autoTrackCustom.pageShare(option);
}
oldPage.apply(this, arguments);
};
复制代码
源码是劫持了微信小程序提供的App, Page函数,然后往里面添加埋点代码,而启动小程序,App函数必然会执行,加载某个页面,Page函数必然会执行,这样预设的埋点代码就在App函数和Page函数执行的时候静默的执行了
劫持了App,Page函数解决了N个页面要写N遍的问题。劫持了App,Page函数,可以实现‘一键埋点’,N个页面只要写一遍就可以了。
虽然劫持了App,Page函数,但是还是有一些问题。
1:无法解决按需埋点,比如说有些页面需要埋,有些页面不需要
2:代码过于集成,会导致不同的埋点需求的代码糅合在一起,埋点代码和埋点代码之间的混乱。
集成和分离举个例子,比如说造一个机器,如果是集成的,机器的某一个地方坏了,可能会导致整个机器都不能用。而分离是把机器拆分成很多个零件模块,零件坏了可以单独修换。
这时候我想到了vue中常常使用的mixins的技巧(混入、有策略性的合并或者继承)可以解决代码复用,抽离的问题。来看看mixins的一个例子:
const mixins = {
// 页面的变量
data: {
apple: 0,
banner: 1,
cat: 3
},
// 页面显示会触发这个函数
onShow() {
console.log('mixins')
},
// 自定义方法1
method1() {
console.log('mixins method1')
},
// 自定义方法2
method2() {
console.log('mixins method2')
}
}
export default {
mixins: [
mixins
],
// 页面的变量
data: {
apple: 100,
},
// 页面显示会触发这个函数
onShow() {
console.log('origin')
},
// 自定义方法1
method1() {
console.log('origin method1')
},
}
复制代码
最终会合并成:
export default {
// 页面的变量
data: {
apple: 100,
banner: 1,
cat: 3
},
// 页面显示会触发这个函数
onShow() {
console.log('mixins')
console.log('origin')
},
// 自定义方法1
method1() {
console.log('origin method1')
},
// 自定义方法2
method2() {
console.log('mixins method2')
}
}
复制代码
so入口处劫持Page函数,在需要埋点的时机比如onShow这个时机用mixins静默注入埋点的代码。
这样就可以不影响到业务代码的情况下,实现业务逻辑和埋点逻辑的分离,不同需求的埋点逻辑的分离,同时支持了集成和按需。 并且可以”一键埋点”。
然后剩下的事件就变成劫持App,Page函数,写一个小程序版的mixins扩展,把不同的埋点需求拆分一个单独的模块,既可以一键集成,又可以按需使用。
前后对比
没改造之前:
// N个这样的页面
Page({
onShow() {
// 业务逻辑
getData().then((res) => {
if (res.data && res.data.code === 1) {
const data = res.data.data
for (let i = 0; i < data.length; i++) {
console.log(i)
}
}
})
// 埋点代码
const title = getTitle()
const url = getUrl()
sensors.track('login', {
title,
url
})
}
})
复制代码
改造后的一键埋点:
// 入口app.js,只要引入一次,所有的页面都不用单独写
import './sensors/one-key.js'
复制代码
// 零侵入业务逻辑
Page({
onShow() {
// 业务逻辑
getData().then((res) => {
if (res.data && res.data.code === 1) {
const data = res.data.data
for (let i = 0; i < data.length; i++) {
console.log(i)
}
}
})
}
})
复制代码
改造后的按需埋点:
import mixins from './sensors/mixins.js'
// 同样零侵入业务逻辑
Page({
// 按需埋点的插槽
mixins: [
mixins
],
onShow() {
// 业务逻辑
getData().then((res) => {
if (res.data && res.data.code === 1) {
const data = res.data.data
for (let i = 0; i < data.length; i++) {
console.log(i)
}
}
})
}
})
复制代码
总结
劫持App,Page函数在小程序中非常有用,可以集成扩展功能。
而mixins是非常好用和通用的写代码技巧,不仅仅是在埋点的逻辑,在业务逻辑上同样非常好用。