作者:feix760

上周处理了一下群活动的 badjs,第一步是摆脱Script error.,捕获异常栈,找到自己是错在哪里~ 分享一下这个步骤

异步的切入点:

1、XMLHttpRequest.prototype.send

2、setTimeout、setInterval

3、define、require

4、Zepto的事件绑定 on 、bind (另外要能off、unbind)

何时引入切入文件:

最简单的方法是在 requirejs 引入后立刻做 define ,require 的切入, 在 Zepto 加载之后做 on,bind 的切入。或者在zepto引入之后做所有的切入。但这样无法对inline进去的代码做切入,比如预加载的代码。既然是移动端,使用window.__defineSetter__去监听define,zepto的出现也许是最好的选择~

下面是主要的代码:

    _.before(XMLHttpRequest.prototype, 'send', function() {                         
        if (this.onreadystatechange) {                                              
            this.onreadystatechange = cat(this.onreadystatechange);                 
        }                                                                           
    });                                                                             

    _.before(window, 'setTimeout setInterval', catArgs);                            

    // 打包后define require有时候会被封在函数里,可以手动暴露到window上             
    _.modify(window, 'define require', funArgsFilter(catArgs));                     

    _.modify(window, 'Jquery Zepto', function($) {                                  
        if ($ && $.fn) {                                                            
            _.modify($.fn, 'on bind', funArgsFilter(catArgs));                      
            _.modify($.fn, 'off unbind', funArgsFilter(uncatArgs));                 
        }                                                                           
        return $;                                                                   
    });                                                                             

    // 手动接口                                                                     
    window.cat = cat;

需要注意:

1、对$.fn.bind做切入,一定也要对$.fn.unbind做切入,使unbind能工作

2、切入函数和被切入函数尽量保持一对一,要不然容易出现未知的问题

3、define、require在打包之后不一定会暴露到window对象上来,可以手动暴露一下

完整代码:

(function() {
    var _ = {
        isFunction: function(fun) {
            return typeof fun === 'function';
        },
        // aop 注入
        before: function(obj, props, hook) {
            props.split(/\s+/).forEach(function(prop) {
                var _fun = obj[prop];
                obj[prop] = function() {
                    var args = hook.apply(this, arguments) || arguments;
                    return _fun.apply(this, args);
                };
            });
        },
        // 监听修改属性
        modify: function(obj, props, modifier) {
            if (!obj.__defineSetter__) {
                return;
            }
            props.split(/\s+/).forEach(function(prop) {
                var value = obj[prop];
                // 如果属性已经存在
                if (typeof value !== 'undefined') {
                    value = modifier.call(this, value);
                }
                obj.__defineSetter__(prop, function(_value) {
                    if (_value !== value) {
                        value = modifier.call(this, _value);
                    }
                });
                obj.__defineGetter__(prop, function() {
                    return value;
                });
            });
        }
    };


    var onthrow = function(e) {
        console.log(e);
        // TODO report
        // 设置一个标志位, window.onerror跳过这个异常,随后设为false
        window._errorthrowing = true;
        throw e;
    };


    /**
     * 包装函数
     */
    function cat(foo) {
        // 防止多次包装
        if (!_.isFunction(foo) || foo.__try) {
            return foo;
        }
        // 保持一对一,要不然容易引起未知的问题
        // 例如: 两次ele.bind('', fun)再ele.unbind('')会有一个无法unbind
        if (foo.__tryer) {
            return foo.__tryer;
        }
        var fun = function() {
            try {
                return foo.apply(this, arguments);
            } catch (e) {
                onthrow(e);
            }
        };
        foo.__tryer = fun;
        fun.__try = foo;
        fun.__proto__ = foo;
        return fun;
    }

    /**
     * 包装参数中的函数
     */
    function catArgs() {
        return [].slice.call(arguments).map(function(fun) {
            return _.isFunction(fun) ? cat(fun) : fun;
        });
    }

    /**
     * 反包装参数中的函数
     */
    function uncatArgs() {
        return [].slice.call(arguments).map(function(fun) {
            return _.isFunction(fun) && fun.__tryer ? fun.__tryer : fun;
        });
    }

    function funArgsFilter(filter) {
        return function(_fun) {
            if (!_.isFunction(_fun) || _fun.__filting) {
                return _fun;
            }
            if (_fun.__filter) {
                return _fun.__filter;
            }
            var fun = function() {
                var args = filter.apply(this, arguments);
                return _fun.apply(this, args);
            };
            _fun.__filter = fun;
            fun.__filting = _fun;
            fun.__proto__ = _fun;
            return fun;
        };
    }

    _.before(XMLHttpRequest.prototype, 'send', function() {
        if (this.onreadystatechange) {
            this.onreadystatechange = cat(this.onreadystatechange);
        }
    });

    _.before(window, 'setTimeout setInterval', catArgs);

    // 打包后define require有时候会被封在函数里,可以手动暴露到window上
    _.modify(window, 'define require', funArgsFilter(catArgs));

    _.modify(window, 'Jquery Zepto', function($) {
        if ($ && $.fn) {
            _.modify($.fn, 'on bind', funArgsFilter(catArgs));
            _.modify($.fn, 'off unbind', funArgsFilter(uncatArgs));
        }
        return $;
    });

    // 手动接口
    window.cat = cat;
})();

原文链接:http://ivweb.io/topic/55e5547acb37d26537b7251f

文章来源于腾讯云开发者社区,点击查看原文