JavaScript基础系列 —— hook

背景:彼时刚毕业没多久,后端遇到一个问题,一个按钮在很多的时间内被双击了,导致可能创建了两笔相同的订单,或者创建了两笔相同的售后。于是有了这样一个需求——如何防止手残党双击,并且是在那么多页面的情况下。思路是:给这些按钮赋予一个类名,最后重新设置这些按钮上绑定的事件函数,在中间插入一些内容(点击后0.5s内不响应操作,代码见下方 防双击通用函数 )。

当时想的好牛逼。连大后端都说,卧槽,牛逼啊,hook啊。于是才知道了hook(钩子)一词。于是才明白,什么算法不算法,也只是名字而已。其实日常中不经意间可能就用了对应的一些概念。顿时,自豪感爆棚。ヽ(•̀ω•́ )ゝ

简单范例

1
2
3
4
5
var _alert = alert;
window.alert = function(s) {
console.log("Hooked!");
_alert(s);
}

业务示例

1
2
lib.api
lib.api 中 匹配 follow 默认打点

重写 appendChild 反劫持

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
var fuck61160200156 = function () {
var db = document.body,
whiteList = ['zhemi.com', 'bbeeii.com'], // 自己网站的脚本服务器列表
reg = new RegExp(whiteList.join('|'), 'gi'); // 其实也只是匹配src 所以只需要符合这里的正则就好了

if (db && db.appendChild) {
// 保存原始引用
db._appendChild = db.appendChild;
// 仅仅覆盖document.body 防止频繁操作和误操作
// 仅仅针对上述场景
db.appendChild = function (dom) {
var domReady = false,
tagName = '';

if (dom && dom.nodeType && dom.nodeType === 1) {
domReady = document.readyState === 'complete' ||
(document && document.getElementById && document.getElementsByTagName); // from Pro JavaScript Techniques 不太准确
if (!window.$ || !domReady || !(window.$ && window.$.isReady)) { // 确定domReady之后执行
if ((dom.nodeName || dom.tagName).toUpperCase() === 'SCRIPT') { // script 标签
if (dom.src && dom.src.search(reg) !== -1) { // 自己的域名
document.write('\<script src=\"' + dom.src + '\" type=\"text\/javascript\" id=\"bdstat\"\>\<\/script\>'); // 用于欺骗运营商广告中的bdstat判断 以及是否生效的测试考虑
return dom; // 该返回的返回
}
}
}
}
// return this._appendChild.call(this, dom); IE 6 7 8 这样的形式不可用会报错
return db._appendChild(dom); // 原始调用
};
}
};

防双击通用函数

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
var avoidDblTrigger = function (selector, eventType, waiting, waitingFunc) {
var $dom = null,
oriBinds = [],// 原来绑定的所有事件函数的散列表
temp;

if ( !selector || !($dom = $(selector)).length ) {
return;
}

eventType = eventType || 'click';
waiting = waiting || 500;
waitingFunc = $.isFunction(waitingFunc) ? waitingFunc : function () {};

// 有绑定过相应事件
if ( (temp = $._data($dom[0]).events) &&
(temp = temp[eventType]) ) {

// 获得 绑定事件的函数数组
// 不可在循环中使用off 因off中会将 $._data($dom[0]).events[eventType] 中的事件绑定函数移除
// 也就类似 循环的过程中改变循环的数组
$.each( temp, function (key, handle) {
if (handle.handler) {
oriBinds.push(handle.handler);
}
});

// 解除绑定
$.each(oriBinds, function (key, handler) {
$dom.off(eventType, handler);
});

$dom.each(function () {

var thisDom = this;

thisDom.isDblTrigger = false;
$(thisDom).on(eventType, function () {
var args = arguments,
// 初始返回值即为undefined
result;

// 在延时时间内再次触发 只针对单个的DOM
// 不对不是连续两次触发过的其他元素影响
if (thisDom.isDblTrigger) {
waitingFunc.apply(thisDom, args);

// 顺便禁掉默认事件
return false;
}

thisDom.isDblTrigger = true;
// 设置定时
setTimeout(function () {
thisDom.isDblTrigger = false;
}, waiting);

$.each(oriBinds, function (key, handler) {

// 原来绑定的所有事件函数 调用
result = handler.apply(thisDom, args);
});

return result;

});
});
}
};