作者: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;
})();