在公司里面实习了一周,前四天都在看代码,终于在第四天快结束的时候接到了一个任务:完成一个打点上报功能,方便统计分析客户的行为。
由于需要设置一些必要的头信息,在项目中已经把JQuery的ajax方法包装成 base.ajax(options)
,调用方法和 $.ajax(options)
一样。看了一下源码,果然,使用的是装饰者。
base是整个项目的基础模块 ,这里提一下。(这里先埋下伏笔)
这里使用装饰者是一种很合适的办法,装饰者本来就是为了在不影响原来的前提下增加额外的东西,返回的装饰者必须完全兼容以前的东西(我实在不知道该怎么表达这个= =,具体可以百度或者去翻设计模式。)。
看到这里一下子就有种接近真相的感觉:打点上报不也是属于附加的功能吗?用装饰者不是正好?于是便有了第一个方案:
//base.js
base.ajax = function(options) {
...
//这里不贴详细代码了,主要的是下面这句:
report(options);
...
}
//report.js
//一些url的白名单映射什么的...
fns = $.isArray(fns) ? fns : [fns];
//根据whiteList检测是否需要打点
var url = param.url;
//不需要上报,直接原路返回
if(whiteList[url] === void 0)
return;
var platform = Life.report.platform;
var p = platform.isPC? 'PC' :
platform.isWx ? 'WX' :
platform.isApp ? 'APP' : 'PC';//取得platform,默认是PC
var commonpart = commonPart(url);//公共部分
$.each(fns, function(v,i) {
//如果是函数,进行装饰
if(typeof v === 'function'){
var originalCb = v;
param[i] = function(){
var args = arguments;//原本的请求返回的结果
var reportData = $.extend(commonpart, actions[p][whiteList[url]](param, args));//需要上报的数据
var params = {
url : Life.report.remoteUrl,
data : reportData,
context : param.context,//保证跟换了回调后上下文不变
success : function(result) {
//result.ret
console.log('上报成功');
originalCb.apply(this, args);
},
//出现了错误后由于恢复现场下面的error不回被替换
//error : function(xhr, status, error) {
// console.error('上传失败.' + status + ':' + error.toString());
// originalCb.apply(this, args);
//}
//other common params
};
$.ajax(params);//上报请求
};
}
});
基本的思路也是替换掉原本的success和error回调,并把原本的success和error包装进上报接口的回调方法中。
使用了装饰者,跑起来应该也没什么问题。然后问了一下Givon的意见,看一下这个方案的可行性。
Givon:要是report.js里面执行的代码出错了岂不是原本的回调不能正常执行?上报只是一个次要功能,不能影响原本重要的功能。
恍然大悟,想了一下,有了改进版本:
function securityFn(){
//把原先的代码移入这里
//这里使用函数包装是为了给try/catch创建一个相对干净的上下文环境,避免try/catch拷贝词法环境带来性能损失。(try/catch的性能问题可以百度一下~)
}
var originalOptions = $.extend({}, options};//保存现场
try{
securityFn();
}
catch(e){
//发生错误,恢复现场
$.extend(options, originalOptions);
}
总的来说,就是把上报的代码放在try/catch中,如果代码出错后会直接恢复原本的回调,不影响原本逻辑的执行。(后来想了一下xhr出错的问题应该屏蔽掉)
现在看起来应该没什么问题了,然后又和Givon讨论了一下:
Givon:base是整个项目的基本模块,不应该去依赖report这个次要模块。
再一次恍然大悟。后台Givon提供了一个解决方案,就是文章标题的主角: trigger
。
trigger
是JQuery提供的触发事件的方法,原生JS的话可以使用 dispatchEvent()
来代替。
使用trigger可以很方便地实现模块之间的解耦:
在需要上报的地方直接触发report事件(trigger('report', data)
),然后在report模块中监听report事件。
这样做的好处除了代码实现简单、逻辑清晰外,还能解决了base和report模块之间的耦合问题:就算report模块没有加载或者出错,base模块只不过是触发了一次report事件,对原本的业务逻辑没有任何影响。
其实使用 trigger
实际上是使用了代理这种方案。想起设计模式中的代理模式,就是为了解决模块之间耦合,使其单独变化的。(当初看的时候没怎么理解代理模式的实际应用场景,现在看来这是一个非常合适的例子)
最后说一下上报方式:
原本的设计中是使用ajax上报,实现起来比较麻烦,后来卡哥提示一下这种(不太重要的)上报一半都是直接把参数塞进 img
便签的src属性后直接插入body,使用get的方式直接上报。
Summary
在这次的小任务中感觉收获良多,自己一开始没有分析好主要矛盾和次要矛盾走了许多弯路,下次要注意。除了巩固了设计模式,也学习了很多有用的技巧。自一次感觉开发经验很重要,Givon和卡哥都是很好的仙贝,这次的任务还没完成,有学到新东西再来分享一下。:)
感觉自己写的东西不太像技术分享反而像流水账日记,辛苦大家了= =。。。