移动端 tryjs 异常捕获

作者: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

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

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

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏python爬虫日记

cxfreeze打包python程序的方法说明(生成安装包,实现桌面快捷方式、删除快捷方式)

python代码文件转exe方法有三种,分别是cx_freeze,py2exe,PyInstaller,这三种方式各有千秋,本人只用过py2exe和cxfree...

784
来自专栏程序员互动联盟

【专业技术】谷歌浏览器是如何编译出来的?

当我们编译chromium时,执行glient背后发生了什么?google为chromium的编译究竟做了哪些事情,我们通常并不需要亲自改这些编译规则,古人尚且...

3575
来自专栏Ryan Miao

照着官方文档学习react

准备 先要准备环境。搭建一个基于webpack的react环境:Hello ReactJS. 一些要点 我在想是否应该完整的记录照抄的过程呢。毕竟已经开始一段,...

3137
来自专栏李成熙heyli

requirejs 源码简析

requirejs 算是几年前一个比较经典的模块加载方案(AMD的代表)。虽然不曾用过,但它对 webpack, rollup 这些后起之秀有不少借鉴的意义...

23010
来自专栏守候书阁

大道至简--API设计的美学

对于前端开发而言,肯定会和API打交道,大家也都会想过怎么设计自己的API。优秀的 API 之于代码,就如良好内涵对于每个人。好的 API 不但利于使用者理解,...

1133
来自专栏编程微刊

markdown模式的一些语法标题一

1031
来自专栏python学习指南

Python爬虫(十一)_案例:使用正则表达式的爬虫

本章将结合先前所学的爬虫和正则表达式知识,做一个简单的爬虫案例,更多内容请参考:Python学习指南 现在拥有了正则表达式这把神兵利器,我们就可以进行对爬...

1885
来自专栏zingpLiu

Linux时间命令

Linux一般有系统时间和硬件时间之分,date命令是显示和操作系统时间;hwclock用来操作硬件时间(日期)。日期和时间很重要,比如错误的日期和时间会导致你...

522
来自专栏debugeeker的专栏

《coredump问题原理探究》Linux x86版4.1节函数的逆向之序言

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/detai...

412
来自专栏逸鹏说道

TypeScript 1.6发布:完全支持React/JSX

2012年,微软推出了一个能够在Node.js上运行的开源语言——TypeScript。作为JavaScript的超集,TypeScript在兼容JavaScr...

2575

扫码关注云+社区