用IE8打开某网页发现弹出了一条信息:stack overflow at line 0,而其他网页显示完全正常,这个时候应该怎么排查错误?我所知道的某个普通web开发人员的排查流程如下:
1、凭经验估计是客户端脚本错误,但是不确定,google一下,发现很多人也碰到过这个问题,看了几篇文章之后估计可能是IE乱七八糟插件搞的,也可能是IE8的bug…
2、使用IE的脚本调试工具无法跟踪到问题发生位置,也不确定问题根源,怂恿并适度威胁打开网页的人重置浏览器设置,未遂;
3、开始思考,堆栈溢出推断可能是递归调用造成的,应该是客户端有问题的javascript造成的,所以注释掉页面中引用的脚本,问题依旧;
4、打开google,baidu,stackoverflow….开始搜索答案,打开FF,Chrome,IETest一个一个测试,竟然没有重现错误,开始大骂微软,诅咒各种国产浏览器…
… …
折腾多次以后,最后实在没有办法,去掉所有引用脚本,html元素自head以下,以div为单位一块一块按需加载。果然,终于在一个img元素上找到了所出现问题的根源。这个img元素看上去非常简单,除了一个src属性,还有一个onerror事件,它是这么写的:
<img alt="" src="http://.../xxx.gif" onerror="javascript:this.src='http://.../nopic.gif'" />
本来onerror这么写的方式通常情况下是不会有问题的。可是实际情况是,如果onerror执行请求的图片(这里是nopic.gif)地址不存在,就会造成一种隐藏的bug,也就是这里提到的造成ie8下的堆栈溢出。
那么怎么理解这样写会造成ie8的堆栈溢出呢?
原因就是onerror事件实际执行的时候,将img元素的src属性定向到某一个图片地址(this.src='http://.../nopic.gif'),但是这个请求的图片地址不存在,所以再次触发onerror事件,再去请求调用不存在的图片地址……如此反复,造成了递归调用。
但是为什么其他很多浏览器都没有这种问题呢?这个就和IE8的脚本引擎有关了,这事不能说太细,简单理解就是传说IE8递归调用次数有个上限,超过了就抛出堆栈溢出异常了,而其他浏览器引擎通常健壮的多,甚至可能直接“吞掉”了这个错误信息,看上去处理的也就更人性化而不会造成开发者的恐慌。到底IE8引擎为什么不能在达到或超过递归调用上限的时候也吞掉这个异常信息呢?kao,我也想知道呢。
这是今年神棍节我在开发中碰到的一行js脚本引发的一个小问题,觉得很好玩,记录一下。
参考:
http://www.nczonline.net/blog/2009/05/19/javascript-stack-overflow-error/
http://www.eggheadcafe.com/software/aspnet/35802087/stack-overflow-at-line-0.aspx
附:正确停止你的ajax请求
现在的web开发中使用ajax的地方非常非常多,借助优秀的javascript脚本库如jquery、yui等等,我们可以轻而易举地写出很多ajax调用。但是您有没有思考过如何停止ajax异步请求呢?实际上我们使用的ajax脚本库本质上都是对XMLHttpRequest对象的封装,而XMLHttpRequest已经提供了停止ajax异步请求的方法:abort()。下面以jquery试举一例:
var xhr = $.ajax({ type: "POST", url: "controller/action", data: json, success: function(msg){ alert( "return ajax request result: " + msg ); } }); //kill the request xhr.abort();
abort方法在某些情况下会很有用。比如如果我们通过一个遮罩层来控制显示ajax请求进度,当在ajax请求没有完成的时候遮罩层可能导致我们无法点击dom元素,想点击dom元素必须等ajax请求结束或者直接重新刷新页面;而有了abort方法,我们完全可以在遮罩层还在的情况下,点击遮罩层的关闭按钮主动停止ajax请求,当然,你知道的,遮罩层关闭按钮必须注册特定的函数来abort异步请求。
参考:
http://msdn.microsoft.com/en-us/library/ms535874(v=VS.85).aspx
http://msdn.microsoft.com/en-us/library/ms535920(v=VS.85).aspx
补充一则:如何在web应用程序中稳定加载jquery?
关于如何加载jquery等公共第三方脚本库的问题经常在论坛里看到有人讨论,看到一个比较好的方案,记录下来。
1、从CDN加载jquery
经常混迹于web开发相关技术论坛的估计都会想到从Google、MS等大公司的CDN上加载jquery。比如从google加载某一版本的jquery可以像下面这样写:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
借助CDN加速,可以缓解服务器的脚本库加载压力,当访问量上去以后,可以为你的站点节省不少流量。
2、google也不能及时访问?
有人会说,这种将压力推给CDN的方式有潜在风险。如果哪一天碰巧借助加载jquery脚本的CDN提供者也出现了问题,jquery文件不是一样加载很慢或者不能加载吗?是的,这种风险确实存在,尤其是在复杂多变的网络环境下。这时候我们就该考虑后路,在最极端的情况下,才让自己的服务器加载jquery脚本:
load jquery
这样看上去才保险一点,但还不是最保险的。
3、使用协议相对应url
通常站点都是http协议,这样像上面那样引人脚本文件访问时是没有问题的。但是如果访问https协议下的网页,浏览器(又是邪恶的IE)可能会给出必要的安全提示:"This Page Contains Both Secure and Non-Secure Items" (详情见这一篇,protocol-relative-url,不知怎么翻译,直译过来就是协议相对url)。解决的方法您应该已经看到了,很简单,再贴一遍吧:
loadjquery
上面介绍的加载静态资源的方式在webform或者mvc中都是适用的。webform中我们还可以配合服务端代码加载公共脚本库,而在mvc中则可以通过UrlHelper或者HtmlHelper扩展方法加载脚本,总体思路大同小异,不再赘述。
最后还是要提一下,前端脚本优化不是简单减少站点上一个脚本引用那么简单。因为我们知道,javascript脚本执行过程中会中断页面加载,直到脚本执行完毕,而这个操作阻塞了页面加载,会造成性能问题。前端javascript脚本优化是技术活,并不是把页面中的脚本合并到一个js文件中就万事大吉了,脚本位置、加载顺序、延迟加载等等都值得细细推敲,牛人老赵的博客挣脱浏览器束缚系列图文并茂,叙事详实而且不乏深度,推荐需要优化前端脚本的朋友看一看学习一下。
参考:
http://www.west-wind.com/weblog/posts/2011/Oct/10/Loading-jQuery-Consistently-in-a-NET-Web-App
http://paulirish.com/2010/the-protocol-relative-url/
http://blog.zhaojie.me/2007/01/break-the-browsers-restrictions-1.html
http://blog.zhaojie.me/2007/01/break-the-browsers-restrictions-2.html
http://blog.zhaojie.me/2007/01/break-the-browsers-restrictions-3.html
http://blog.zhaojie.me/2007/01/break-the-browsers-restrictions-4.html