欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Uniapp微信小程序?qū)崿F(xiàn)全局事件監(jiān)聽并進行數(shù)據(jù)埋點的方法

 更新時間:2022年11月14日 10:12:45   作者:flymyd  
niapp起源?uni-app是一個使用Vue.js開發(fā)所有前端應(yīng)用的框架,下面這篇文章主要給大家介紹了關(guān)于Uniapp微信小程序?qū)崿F(xiàn)全局事件監(jiān)聽并進行數(shù)據(jù)埋點的相關(guān)資料,需要的朋友可以參考下

零、前言

最近接到需求,領(lǐng)導(dǎo)希望使用微信開放平臺上免費的We分析進行數(shù)據(jù)埋點,但又不希望在現(xiàn)有uniapp開發(fā)的微信小程序代碼上做侵入式修改,筆者奉命進行了技術(shù)調(diào)研,考慮通過劫持事件的方式來實現(xiàn)捕獲特定事件并上傳分析平臺的功能。

需要特別注意的是,微信小程序是不能得到document對象的,$el上掛載的也是undefined,自然也就不能通過全局addEventListener的方式來監(jiān)聽特定事件。在調(diào)研中想到可以通過劫持小程序的自定義組件構(gòu)造器Component()來實現(xiàn)事件的監(jiān)聽。

為了便于理解,部分?jǐn)?shù)據(jù)結(jié)構(gòu)通過TypeScript接口形式進行描述。

一、軟件環(huán)境

  • HbuilderX 3.4.7.20220422
  • 微信開發(fā)者工具 Stable 1.05.2203070
  • 小程序基礎(chǔ)庫版本 2.24.4 [749]

二、相關(guān)分析及實現(xiàn)

uniapp編譯微信小程序時對于事件的處理分析

部分知識via掘金:http://www.dbjr.com.cn/article/267434.htm

uniapp使用了uni-app runtime這個運行時將小程序發(fā)行代碼進行打包,實現(xiàn)了Vue與小程序之間的數(shù)據(jù)及事件同步。

源Vue模板及編譯產(chǎn)物wxml對照

uniapp的模板編譯器代碼在/Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/uni-template-complier下。

首先以一個簡單的Vue模板為例,觀察uniapp是如何將Vue template編譯為wxml的:

<template>
  <div @click="add();subtract(2)" @touchstart="mixin($event)">{{ num }}</div>
</template>

編譯結(jié)果為:

<view 
    data-event-opts="{{
        [
            ['tap', [['add'],['subtract',[2]]] ],
            ['touchstart', [['mixin',['$event']]] ]
        ]
    }}"
    bindtap="__e" bindtouchstart="__e"
    class="_div">
    {{num}}
</view>

可以看到,uniapp將tap和touchstart事件綁定到__e函數(shù)上,然后將事件對應(yīng)的動作放到了名為eventOpts的dataset中。

data-event-opts

data-event-opts非常重要。data-event-opts是一個二維數(shù)組,每個子數(shù)組代表一個事件類型。事件類型有兩個值,第一個表示事件類型名稱,第二個表示觸發(fā)事件函數(shù)的個數(shù)。事件函數(shù)又是一個數(shù)組,第一個值表述事件函數(shù)名稱,第二個是參數(shù)表。下面用TypeScript的類型聲明方式進行簡單描述:

//data-event-opts是一個二維數(shù)組,每個子數(shù)組代表一個事件類型EventTypes
const dataEventOpts: EventTypes;
interface EventTypes {
  [index:number]: EventType;
}
//事件類型的描述為EventType。EventType只有兩個元素,也就是說EventType.length===2
interface EventType {
  //EventType的第一個元素是事件類型名稱
  //第二個元素是事件函數(shù)的數(shù)組EventFuncList,數(shù)組內(nèi)元素為被觸發(fā)的事件函數(shù)
  [index:number]: string | EventFuncList;
}
interface EventFuncList {
  //事件函數(shù)依舊是一個數(shù)組
  [index:number]: EventFunc;
}
//事件函數(shù)的元素為1或2個,分別是事件函數(shù)名稱和參數(shù)表Array<any>
interface EventFunc {
  [index:number]: string | Array<any>;
}

對照模板,就可以得出如下推論:

['tap',[['add'],['subtract',[2]]]]表示事件類型為tap,觸發(fā)函數(shù)有兩個,一個為add函數(shù)且無參數(shù),一個為subtract且參數(shù)為2。 ['touchstart',[['mixin',['$event']]]]表示事件類型為touchstart,觸發(fā)函數(shù)有一個為mixin,參數(shù)為$event對象。

不難看出,我們在進行事件捕捉時,只需要讀取到data-event-opts[i][0]就可以得到每個事件的類型。

handleEvent事件:__e

所有的事件都會調(diào)用__e事件,也就是handleEvent。在上文的模板中,handleEvent做了如下操作:

1、拿到點擊元素上的data-event-opts屬性:[['tap',[['add'],['subtract',[2]]]],['touchstart',[['mixin',['$event']]]]]

2、根據(jù)點擊類型獲取相應(yīng)數(shù)組,比如bindTap就取['tap',[['add'],['subtract',[2]]]]bindtouchstart就取['touchstart',[['mixin',['$event']]]]

3、依次調(diào)用相應(yīng)事件類型的函數(shù),并傳入?yún)?shù),比如tap調(diào)用this.add();this.subtract(2)

uniapp對mp-wx的相關(guān)處理在/Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/uni-mp-weixin下。

// @dcloudio/uni-mp-weixin/dist/index.js:1302
function handleEvent (event) {
  event = wrapper$1(event);

  // [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
  const dataset = (event.currentTarget || event.target).dataset;
  if (!dataset) {
    return console.warn('事件信息不存在')
  }
  const eventOpts = dataset.eventOpts || dataset['event-opts']; // 支付寶 web-view 組件 dataset 非駝峰
  if (!eventOpts) {
    return console.warn('事件信息不存在')
  }

  // [['handle',[1,2,a]],['handle1',[1,2,a]]]
  const eventType = event.type;

  const ret = [];

  eventOpts.forEach(eventOpt => {
    let type = eventOpt[0];
    const eventsArray = eventOpt[1];

    const isCustom = type.charAt(0) === CUSTOM;
    type = isCustom ? type.slice(1) : type;
    const isOnce = type.charAt(0) === ONCE;
    type = isOnce ? type.slice(1) : type;

    if (eventsArray && isMatchEventType(eventType, type)) {
      eventsArray.forEach(eventArray => {
        const methodName = eventArray[0];
        if (methodName) {
          let handlerCtx = this.$vm;
          if (handlerCtx.$options.generic) { // mp-weixin,mp-toutiao 抽象節(jié)點模擬 scoped slots
            handlerCtx = getContextVm(handlerCtx) || handlerCtx;
          }
          if (methodName === '$emit') {
            handlerCtx.$emit.apply(handlerCtx,
              processEventArgs(
                this.$vm,
                event,
                eventArray[1],
                eventArray[2],
                isCustom,
                methodName
              ));
            return
          }
          const handler = handlerCtx[methodName];
          if (!isFn(handler)) {
            throw new Error(` _vm.${methodName} is not a function`)
          }
          if (isOnce) {
            if (handler.once) {
              return
            }
            handler.once = true;
          }
          let params = processEventArgs(
            this.$vm,
            event,
            eventArray[1],
            eventArray[2],
            isCustom,
            methodName
          );
          params = Array.isArray(params) ? params : [];
          // 參數(shù)尾部增加原始事件對象用于復(fù)雜表達式內(nèi)獲取額外數(shù)據(jù)
          if (/=\s*\S+\.eventParams\s*\|\|\s*\S+\[['"]event-params['"]\]/.test(handler.toString())) {
            // eslint-disable-next-line no-sparse-arrays
            params = params.concat([, , , , , , , , , , event]);
          }
          ret.push(handler.apply(handlerCtx, params));
        }
      });
    }
  });

  if (
    eventType === 'input' &&
    ret.length === 1 &&
    typeof ret[0] !== 'undefined'
  ) {
    return ret[0]
  }
}

微信小程序自定義組件Component

mp-wx中的Component文檔:https://developers.weixin.qq.com/miniprogram/dev/reference/api/Component.html

構(gòu)造器Component()

在uniapp-mp-wx中,組件的裝載是通過實例化Component進行的。uniapp會默認裝載如下8個參數(shù):

interface optionsList {
  options: Object | Map<any, any>,
  data: Object,
  properties: Object | Map<any, any>,
  behaviors: string | Array<any>,
  lifetimes: Object,
  pageLifetimes: Object,
  methods: Object,
  created: Function
}

并且在methods中注入如下兩個函數(shù):

methods: {
      __l: handleLink,   //建立組件父子關(guān)系
      __e: handleEvent   //事件處理器
}

劫持自定義組件構(gòu)造器Component

劫持Component的構(gòu)造器,在每個組件的__e中注入自定義的事件劫持器eventProxy

// 劫持Component
const _componentProto_ = Component;
Component = function(options) {
  //options.methods內(nèi)有uniapp注入的事件處理器__e及mpHook
  Object.keys(options.methods).forEach(methodName => {
    //劫持事件處理器__e
    if (methodName == "__e") {
      eventProxy(options.methods, methodName)
    }
  })
  _componentProto_.apply(this, arguments);
}

通過劫持事件處理器__e,我們可以實現(xiàn)觸發(fā)事件時執(zhí)行我們想要的邏輯了。

分析事件對象并編寫事件處理器劫持函數(shù)eventProxy

微信小程序事件對象描述文檔:https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html#%E4%BA%8B%E4%BB%B6%E5%AF%B9%E8%B1%A1

在上一步里我們劫持了Component,并且成功獲得了事件處理器__e,那么編寫針對事件處理器的劫持函數(shù)吧。

function eventProxy(methodList, methodName) {
  const _funcProto_ = methodList[methodName];
  methodList[methodName] = function() {
    _funcProto_.apply(this, arguments);
    let prop = {};
    if (isObject(arguments[0])) {
      if (Object.keys(arguments[0]).length > 0) {
        //arguments[0]即為事件對象的屬性
      }
    }
  }
}

uniapp-mp-wx中,事件對象通常具有如下屬性:

["type", "timeStamp", "target", "currentTarget", "mark", "detail", "touches", "changedTouches", "mut", "_userTap", "mp", "stopPropagation", "preventDefault"]

其中,對于數(shù)據(jù)埋點尤其有用的是如下四個屬性:

  • type:描述事件類型。常見種類有tap(click)、input、blur、focus等
  • currentTarget:事件綁定的當(dāng)前組件

從Vue模板編譯一節(jié)中可知,我們應(yīng)該關(guān)注currentTarget.dataset.eventOpts這個屬性,這里記載了事件被觸發(fā)時的一些信息。

interface currentTarget {
  id: string,				//當(dāng)前元素的id
  dataset: Object		//當(dāng)前元素上由data-開頭的自定義屬性組成的集合
}

mark:可以使用 mark 來識別具體觸發(fā)事件的 target 節(jié)點。此外, mark 還可以用于承載一些自定義數(shù)據(jù)(類似于 dataset )。

當(dāng)事件觸發(fā)時,事件冒泡路徑上所有的 mark 會被合并,并返回給事件回調(diào)函數(shù)。(即使事件不是冒泡事件,也會 mark 。)

如果想要得到一些詳細的錨點數(shù)據(jù),可以在代碼中做一些mark標(biāo)記。

<view mark:myMark="last" bindtap="bindViewTap">
  <button mark:anotherMark="leaf" bindtap="bindButtonTap">按鈕</button>
</view>
<script>
Page({
  bindViewTap: function(e) {
		//Object.keys(e.mark)即為觸發(fā)事件的節(jié)點經(jīng)過的所有mark
    e.mark.myMark === "last" // true
    e.mark.anotherMark === "leaf" // true
  }
})
</script>

detail:自定義事件所攜帶的數(shù)據(jù),如表單組件的提交事件會攜帶用戶的輸入,媒體的錯誤事件會攜帶錯誤信息,詳見組件定義中各個事件的定義。

點擊事件的detail 帶有的 x, y 同 pageX, pageY 代表距離文檔左上角的距離。

這里給出tap及input事件返回的detail結(jié)構(gòu):

interface tapDetail {
  x: number,  //距離文檔X軸零點的距離,零點為文檔左上角
  y: number		//距離文檔Y軸零點的距離
}

interface inputDetail {
  value: string,		//用戶輸入的值
  cursor: number,		//觸發(fā)事件時光標(biāo)所在的位置
  keyCode: number		//觸發(fā)事件時用戶輸入的keyCode
}

結(jié)合如上屬性,簡單地完善一下事件劫持器吧:

function eventProxy(methodList, methodName) {
  const _funcProto_ = methodList[methodName];
  methodList[methodName] = function() {
    _funcProto_.apply(this, arguments);
    let prop = {};
    if (isObject(arguments[0])) {
      if (Object.keys(arguments[0]).length > 0) {
        //記錄觸發(fā)頁面信息
        const pages = getCurrentPages();
        const currentPage = pages[pages.length - 1];
        prop["$page_path"] = currentPage.route; //頁面路徑
        prop["$page_query"] = currentPage.options || {}; //頁面攜帶的query參數(shù)
        const type = arguments[0]["type"];
        const current_target = arguments[0].currentTarget || {};
        const dataset = current_target.dataset || {};
        prop["$event_type"] = type;
        prop["$event_timestamp"] = Date.now();
        prop["$element_id"] = current_target.id;
        const eventDetail = arguments[0].detail;
        prop["$event_detail"] = eventDetail;
        if (!!dataset.eventOpts && type) {
          if (type == "tap") { //只記錄點擊事件
            const event_opts = dataset.eventOpts;
            if (Array.isArray(event_opts) && event_opts[0].length === 2) {
              let eventFunc = [];
              event_opts[0][1].forEach(event => {
                eventFunc.push({
                  name: event[0],
                  params: event[1] || ''
                })
              })
              prop["$event_function"] = eventFunc;
            }
          }
          postWeData(prop); //在此處上傳記錄的事件數(shù)據(jù)
        }
      }
    }
  };
}

三、完整代碼結(jié)構(gòu)

(function() {
  const isObject = function(obj) {
    if (obj === undefined || obj === null) {
      return false;
    } else {
      return toString.call(obj) == "[object Object]";
    }
  };
  // 劫持Component
  const _componentProto_ = Component;
  Component = function(options) {
    //options.methods內(nèi)有uniapp注入的事件處理器__e及mpHook
    Object.keys(options.methods).forEach(methodName => {
      if (methodName == "__e") {
        //劫持事件處理器
        eventProxy(options.methods, methodName)
      }
    })
    _componentProto_.apply(this, arguments);
  }

  function eventProxy(methodList, methodName) {
    //事件處理器的劫持
  }

  const postWeData = function(data) {
    //埋點上傳器
    console.log(data)
  }
})()

使用:在項目的main.js里引入即可

//main.js
import './common/WeData/index.js'

四、后記

上述事件劫持器只是一個例子,實現(xiàn)了基本的tap事件記錄。實際上筆者通過擴展配置讀取的方式來完成更加便捷的埋點操作,后續(xù)只需產(chǎn)品給出希望收集的事件名,開發(fā)在固定的配置文件中寫好代碼中事件觸發(fā)的函數(shù)名即可實現(xiàn)tap白名單記錄功能。更加詳細的埋點功能可以通過閱讀分析事件對象小節(jié)來擴展,在此僅做拋磚引玉。

到此這篇關(guān)于Uniapp微信小程序?qū)崿F(xiàn)全局事件監(jiān)聽并進行數(shù)據(jù)埋點的文章就介紹到這了,更多相關(guān)Uniapp微信小程序全局事件監(jiān)聽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何在父窗口中得知window.open()出的子窗口關(guān)閉事件

    如何在父窗口中得知window.open()出的子窗口關(guān)閉事件

    在父窗口中得知window.open()出的子窗口關(guān)閉事件的方法有很多,在本文將為大家詳細介紹下,感興趣的朋友可以參考下
    2013-10-10
  • 微信小程序?qū)崿F(xiàn)圖片壓縮

    微信小程序?qū)崿F(xiàn)圖片壓縮

    這篇文章主要為大家詳細介紹了微信小程序?qū)崿F(xiàn)圖片壓縮,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • 如何進行微信公眾號開發(fā)的本地調(diào)試的方法

    如何進行微信公眾號開發(fā)的本地調(diào)試的方法

    這篇文章主要介紹了如何進行微信公眾號開發(fā)的本地調(diào)試的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-06-06
  • JS中confirm,alert,prompt函數(shù)區(qū)別分析

    JS中confirm,alert,prompt函數(shù)區(qū)別分析

    JS中confirm,alert,prompt函數(shù)使用區(qū)別有哪些呢?
    2011-01-01
  • 微信小程序前端promise封裝代碼實例

    微信小程序前端promise封裝代碼實例

    這篇文章主要介紹了微信小程序前端promise封裝代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-08-08
  • Javascript基于OOP實實現(xiàn)探測器功能代碼實例

    Javascript基于OOP實實現(xiàn)探測器功能代碼實例

    這篇文章主要介紹了Javascript基于OOP實實現(xiàn)探測器功能代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-08-08
  • 解決JS中乘法的浮點錯誤的方法

    解決JS中乘法的浮點錯誤的方法

    本篇文章主要介紹了解決JS中乘法的浮點錯誤的方法。需要的朋友可以過來參考下,希望對大家有所幫助
    2014-01-01
  • JavaScript編程的10+最佳實踐解決方案

    JavaScript編程的10+最佳實踐解決方案

    在現(xiàn)代Web開發(fā)中,JavaScript已經(jīng)成為無法替代的核心技術(shù),在現(xiàn)代Web開發(fā)中,JavaScript已經(jīng)成為無法替代的核心技術(shù),本文將通過代碼示例詳細介紹一些實踐解決方案,感興趣的同學(xué)可以參考下
    2023-06-06
  • js實現(xiàn)字符串轉(zhuǎn)日期格式的方法

    js實現(xiàn)字符串轉(zhuǎn)日期格式的方法

    這篇文章主要介紹了js實現(xiàn)字符串轉(zhuǎn)日期格式的方法,涉及javascript針對字符串與日期操作的相關(guān)技巧,需要的朋友可以參考下
    2015-05-05
  • 熱點新聞滾動特效的js代碼

    熱點新聞滾動特效的js代碼

    我們在很多大型門戶網(wǎng)站都會有看到有些一熱點新聞都會一直向上滾動,下面我就來給大家推薦一款Javascript中熱點新聞滾動特效代碼,有需要了解的朋友可以參考一下
    2013-08-08

最新評論