我是Chrome的忠实用户,而且此前也帮Chrome抓到了好几个小虫,并提交给了Google——当然了,他们FixBug说不定和我一点关系都没有,这也是很正常的。
然后今天就又捉到了一只虫子。
在浏览器中,载入javascript文件自然是使用script标签(动态加载也需要创建这货)。然后这个标签有一个defer属性(IE6及其同伙看到这里就可以关闭浏览器了,下面没你们什么事)。这个属性的作用,是告诉浏览器(Chrome、Firefox、Opera和IE10+,山寨货这里不考虑):我现在被加载,但在整个页面都加载完毕后才运行我里面的代码。
这货配合async标签不错,异步加载,且等到整个界面都加载完成后再阅读,基本不会对HTML部分产生BLOCK——当然了,网络环境太渣就不考虑了。
一般来说,当body的onload事件结束后,才会执行defer的script。多个defer的script也是按照他们的先后顺序来执行——如果使用了async标签,那么是script标签的加载结束顺序为真正的执行顺序,所以先后是不一定的——这个大家可以看webkit的resource管理源码,包括css、script和图片,都是这么一个流程——话说去年发现的webkit的一个bug就是在图片的resource的管理上出了问题,那个模块是三丧提供的,鄙视一个。
有点扯远了,拉回来。
这次抓到的Chrome的小虫,就是这个defer属性。
按照正常流程,我只要页面加载了,那么script块就会运行——运行实际要么是加载完成时(没有defer),要么是整个页面加载完成时(设置了defer)。
如果你写了一个页面,第一次加载的时候,的确是这样。
每次按F5(或者执行window.location.reload()),也都是正常加载的。
这些都很正常。
但如果你进入新的页面然后按后退键返回(或者window.history.back()),此时你会惊喜地发现defer的代码块没有执行……
有时,甚至是通过anchor标签超链接跳转到一个新页面,这个新页面defer了同样的script,它也会不执行。
由于工作项目换了,所以没用GDB跟进去,但预估还是resource管理的问题,即Chrome的缓存机制将script都缓存了,导致defer没有接受到onload事件,所以也就不会在页面整体onload后执行defer的代码块——它压根就认为这些东西还没好呢。
解决方法其实很简单,不用defer标签,将所有需要defer的script块都挪到整个页面的最后,就完了。
当然,这个做法和defer还是有本质区别的,那就是defer是在script位置对文件加载,加载完成后再加载解析和渲染其后的内容;但如果你是把script搬到最后,那script加载就挪到了页面其它内容之后。考虑上加载时的网络环境很糟,可能就会发生页面已经显示了,但交互用的script还没OK的情况,从体验上来说就比defer要糟。
当然,更常用的方法当然不会如此无脑,而是将script中的执行代码放在一个callback中,然后将这个callback放到最后运行,或者在body的onload中调用。
此问题在Firefox中没有,算是Chrome特有吧——当然,Safari和Opera现在也是Webkit,但还没有测试这个问题。不排除是Chrome的Blink才有的问题。。。