前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >移动端tryjs异常捕获

移动端tryjs异常捕获

作者头像
IMWeb前端团队
发布2017-12-29 17:02:51
8340
发布2017-12-29 17:02:51
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队

上周处理了一下群活动的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;
})();
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 异步的切入点:
  • 何时引入切入文件:
  • 需要注意:
  • 完整代码:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档