关于站点广告位对接的一点思考

场景描述

  公司主要从事软件分发和互联网流量运营行业,与各个软件及资源下载站点都有着合作。而大部分站点为了承载更多的PV,一般是用CMS生成静态文件以供访问,减小服务器运算压力。

  那么广告位的调整,如果每次都要全站生成,十分浪费时间而且有挂掉服务器的风险,相比之下当然是直接拿JS来操作广告位更加便捷。所以每到合作站点续费的时候,都要涉及到广告位的前端对接。

  来到公司也有快一年了,对接了大大小小上百个站点(从我工作路径的文件夹数目看,截止目前127个左右)。对接了这么多站点的广告位,确实踩了很多很多坑,十分辛苦,也在摸爬滚打中,慢慢熟悉了一些套路。
  

第一个问题:稳定的代码基

  由于各个站点规模和性质的不同,导致其代码结构和组织方式参差不齐。大的站点还好些,至少有着专业的团队做技术支撑,技术架构清晰,代码目的明确,进行对接不会太痛苦。而不巧的是,软件站和资源站站长,有不少都是个人站长,非常可能不懂技术。而各种站群和仿站的出现,又使得代码结构混乱,意义不明,打开网站控制台几十行触目惊心的报错,还存在命名空间污染等问题。

  最让人抓狂的是,有些古老的站点,使用Table进行布局,大量使用Flash,服务端使用windows server全家桶。这样的站点并没有引用jQuery,这导致了很多不便,其中就包括DOM元素选择和一系列兼容问题。
  
就连外星人都用jQuery!

  又或者,这个站点加载了太多的其他广告,而且占用了’$’命名空间,使得jQuery无法正确运作,但你还偏偏不能直接覆盖掉’$’的控制权,因为这些广告依赖于它用’$’定义的工具类,这样做会导致其他广告无法显示。

  啰嗦了这么久,吐槽了这么多,我们来提炼下需求:

如何在站点JS代码参差不齐的情况下,提供稳定的代码基,屏蔽底层的兼容性问题,封装常见的DOM操作,并且能较为轻松地注入到页面环境中,对命名空间产生最小的影响?

解决方式

  稳定的代码基?兼容性问题?封装DOM操作?这怎么听起来这么耳熟。。。
  不知道你们怎么想,反正我第一个想到的就是jQuery。
  讲是这么讲,那么这个问题怎么解决呢?

  Talk is cheap,show me the code!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/********************************
* 声明withJQ函数
* 作用:判断页面内是否有jQuery,
有就直接调用callback,
没有就加载百度CDN的再调用
*********************************/
function withJQ(callback) {
if (typeof jQuery === 'undefined') {
//创建跨域(CrossDomain)脚本对象cdjs
var cdjs = document.createElement("script");
//设置百度CDN的JQ源作为src
var requestHandler = "http://apps.bdimg.com/libs/jquery/1.6.4/jquery.min.js";
cdjs.src = requestHandler;
cdjs.type = "text/javascript";
//注册事件判断是否加载完成,由于这个时候还没有加载JQ,要充分考虑原生JS的兼容性问题
cdjs.onload = cdjs.onreadystatechange = function() {
if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
//释放'$'命名空间,防止冲突
jQuery.noConflict();
//防御性编码,要求输入的回调参数存在,且必须为函数
if (callback && typeof callback === "function") {
callback();
}
//注销事件
cdjs.onload = cdjs.onreadystatechange = null;
}
};
//挂载cdjs到head中,触发加载
document.getElementsByTagName('head')[0].appendChild(cdjs);
} else {
//如果JQ已经存在,直接执行回调,不再重复加载
callback();
}
}
//调用函数,按需加载JQ
withJQ(function() {
//DOM加载完毕,开始执行代码
jQuery(function($) {
// 你可以在这里为所欲为了。。。
});
});

第二个问题:及时的操作

  解决了代码基的问题,我们又遇到了新的问题。想必各位熟悉JQ的前端同学都知道,上面的代码中,我使用了

1
jQuery(function($){});

  这是$(document).ready() 的简写,内部的 jQuery 代码依然使用 $ 作为别名,而不管全局的 $ 为何。这样写的好处是,能确保在代码执行的时候,DOM已经加载完毕。

  当时我就觉得,老哥,稳!

  然而想法是美好的,现实却是残酷的。我绝望地发现,正如语文课本上的那首诗:

在山的那边,依旧是山

山那边的山啊,铁青着脸

给我的幻想打了一个零分!

  这种写法固然出发点很美好,但是令人不能如愿的地方在于,有的站点的广告实在太多,太疯狂了。。。这就导致$(document).ready()事件始终触发不了,即使我司的带宽已经达到了千兆G管的十分之一(百兆企业光纤还说得这么洋气作甚),那可耻的加载圆圈还是久久不会消去。

  市场部的同学在旁边用期待的眼神看着我撸码,场面一度十分尴尬。。。

解决方式

  于是我又去琢磨了下解决方案,经过无数次的Google,以及解读竞争对手家的JS源码(虽然那源码经过压缩混淆之后,连它亲妈都快不认识它了),我找到了如下解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//调用函数,按需加载JQ
withJQ(function() {
// 由于没有用jQuery(function($){})的闭包,Just in case,避免命名冲突
var $=jQuery;
/*
* 匿名函数延时递归法,
* 一旦元素加载完成,就立即执行相应脚本,
* 好处是不用等DOM全部加载完成,
* 坏处是定时器会加重浏览器负担
*/
(function(){
if($('.someElement').length==0){
//arguments.callee是一个指针,指向拥有它的函数
//值得注意的是,在严格模式下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee()。
setTimeout(arguments.callee,800);
}else{
$('.someElement').html('<a href="foo" target="_blank">Some HTML fragments.</a>');
}
})()
});

  好,以上就是我对工作中两个坑的总结,之后可能还会继续更新。

  如果我帮到了你,还麻烦你动一动发财的小手,赏我一杯咖啡喝喝呗~⬇️