好玩的JS系列--小程序数据埋点

作者: 飙猪狂 | 来源:发表于2018-12-12 14:23 被阅读54次

    前言

      最近负责的一个项目,项目的客户端是微信小程序。客户提了需求,需要进行数据埋点,收集用户行为数据,为后续提升用户体验及产品优化提供基础数据收集。于是在一个风和日丽的日子,撸起袖子动手干。

    思路

      先看看客户端常见的数据埋点方案如下图。

    数据埋点方案.png
      侵入式埋点这个如果在页面少,数据收集需求变化不大的情况下,还是挺经济实惠的。鉴于当前项目页面较多,数据收集的需求变化也在不断的调整,所以侵入式埋点的方案不适用本项目,只有松耦合这个方案了。

    小程序的特点

      根据松耦合埋点方案,首先要解决的问题是拦截小程序所有控件的操作数据。在Web中有BOM对象及DOM对象可以给我们进行全方位的操作,特别是BOM对象,可以进行全局事件拦截。而小程序类似BOM对象的主要有下面三个对象:App、Page、Component。

    • App:App对象有且只有一个,属于小程序的总控对象,App下面包含着n个Page对象。
    • Page:Page则通过字面理解就是页面的总控对象,一个小程序可以有多个Page,每一个Page对象对应一个页面,Page下面可以包含n个Component及其他普通的小程序组件。
    • Component:Component则是自定义组件对象,类似于Page,只明生命周期及一些特性上有所区别,自定义组件对象在实际项目中比较常用,主要用来解决小程序页面堆栈太小的问题,这个后续再写另外一篇来讲。

      小程序的事件机制也是采用与JavaScript一样的事件机制,捕获->执行->冒泡的机制,JavaScript在BOM及DOM对象中都提供了addEventListener这种订阅/发布机制,使我们可以轻松的拦截所有的事件,又不影响现有的流程及代码,从而实现松耦合埋点方案。但是小程序没有提供addEventListener的订阅/发布机制,没有办法通过这样子的方案来实现。

    1.拦截器方式

    翻了官网,发现没有办法统一的处理,那么只能退而求其次,减少对现有的代码的侵入,于是想到了拦截器的机制来实现。

      看看下面小程序原生的代码

    App({
      onLaunch(options) {
        // Do something initial when launch.
      },
      onShow(options) {
        // Do something when show.
      },
      onHide() {
        // Do something when hide.
      },
      onError(msg) {
        console.log(msg)
      },
      globalData: 'I am global data'
    })
    

      提供拦截器,这里只拿App的onLaunch事件进行示例,其他事件都差不多这样处理即可。

    var appFilter = function(config){
      if(config.onLaunch){
        let _onLaunch = config.onLaunch;
        config.onLaunch = function(ops){
             //在这里干埋点的事
            //例如存储数据、上送数据
             _onLaunch.call(this);//调用原来的执行逻辑
        }
      }
      return config;
    }
    
    
    //App使用拦截器示例
    App(appFilter({
      onLaunch(options) {
        // Do something initial when launch.
      },
      onShow(options) {
        // Do something when show.
      },
      onHide() {
        // Do something when hide.
      },
      onError(msg) {
        console.log(msg)
      },
      globalData: 'I am global data'
    })
    )
    

      这个也是JS好玩的地方,一切皆为对象。通过新增一个Function对象,增强方法之后,把旧的Function对象进行替换。上面的App的onLaunch方法,经过appFilter过滤器的处理后,就替换成过滤器里面加了埋点事件的新方法了,对于开发人员来讲,不需要去改动原来App.onLaunch事件里面的代码,目的达到了,但是不够完美,在写过滤器的实现逻辑时,给了我一个灵感,我是否可以用这种方式来实现不侵入代码的埋点?答案是可以的。

    2.增强扩展方式

      利用新增一个Function对象,增强方法之后,把旧的Function对象进行替换这样的原理,依次将App、Page、Component这三个对象进行增强扩展,示例代码如下。

    //先把原生的三个对象保存起来
    const originalApp = App,
          originalPage = Page,
          originalComponent = Component;
    //在原生的事件函数里面,添加数据埋点,并替换成新的事件函数
    const _extendsApp = function (conf, method) {
      const _o_method = conf[method];
      conf[method] = function (ops) {
        //在此处进行数据埋点
        if (typeof _o_method === 'function') {
          _o_method.call(this, ops);
        }
      }
    }
    
    //重新定义App这个对象,将原来的App对象覆盖
    App = function(conf){
      //定义需要增强的方法
      const methods = ['onLaunch', 'onShow', 'onHide', 'onError']
    
      methods.map(function (method) {
        _extendsApp(conf, method);
      })
      //另外增强扩展埋点上送的方法
      conf.william = {
        addActionData: function (ops) {
          console.log('addActionData');
        },
        addVisitLog: function (ops) {
          console.log('addVisitLog');
        }
      }
      return originalApp(conf);
    }
    //Page及Component对象类似App的处理即可
    

      至此,整个小程序的生命周期都在掌握之中了,可以按需采集对应的数据,并且对于开发人员来讲,还不需要去修改及调整代码,松耦合埋点方案搞定。

    3.增强扩展+订阅/发布

      上面的实现方案虽然实现了松耦合,但是个人觉得还不够完美,埋点的动作必须要写在增加的方法里面,这样子可维护性较差,也不够灵活。解耦这事现在对我来说信手拈来,祭出了我的EventHub(基于订阅/发布模式实现的消息总线),完美。

    //引用EventHub
    import EventHub from '../../utils/eventhub.min';
    //先把原生的三个对象保存起来
    const originalApp = App,
          originalPage = Page,
          originalComponent = Component;
    //在原生的事件函数里面,添加数据埋点,并替换成新的事件函数
    const _extendsApp = function (conf, method) {
      const _o_method = conf[method];
      conf[method] = function (ops) {
        //在此处进行数据埋点
        //此处改成消息发布
        if (typeof EventHub != "undefined") {
          EventHub.emit('app' + method, ops);
        }
        if (typeof _o_method === 'function') {
          _o_method.call(this, ops);
        }
      }
    }
    
    //重新定义App这个对象,将原来的App对象覆盖
    App = function(conf){
      //定义需要增强的方法
      const methods = ['onLaunch', 'onShow', 'onHide', 'onError']
    
      methods.map(function (method) {
        _extendsApp(conf, method);
      })
      //另外增强扩展埋点上送的方法
      conf.william = {
        addActionData: function (ops) {
          console.log('addActionData');
        },
        addVisitLog: function (ops) {
          console.log('addVisitLog');
        }
      }
      return originalApp(conf);
    }
    //Page及Component对象类似App的处理即可
    

      这样子就可以把埋点处理的逻辑抽离到另外一个JS文件中去实现。

    EventHub.on('apponLaunch',function(ops){
        //在这里可以处理数据埋点的事
    })
    

    延展性思考

      基于上述的实现方案,我们除了增强生命周期,也可以像上面那样去增加公用的方法,例如App.william.addActionData,更可以通过增强setData方法来进行数据溯源或者进行差量比较来提升性能等方式。

    相关文章

      网友评论

        本文标题:好玩的JS系列--小程序数据埋点

        本文链接:https://www.haomeiwen.com/subject/hyoehqtx.html