JavaScript基础系列 —— hook

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

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

简单范例

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

业务示例

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

重写 appendChild 反劫持

1
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
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;

            });
        });
    }
};