在我上篇文章里,我提到一种使用iframe完成无阻塞脚本加载的方式,因为我对iframe的偏见很大,所以上篇文章里我没有展开讨论这个问题。
文章发表后有位网友问了我这样一个问题,下面是他问题的原文,首先看他发给我的截图,如下所示:
我一个电商后台系统,用的表格控件是flexigrid,,里面是个iframe来的,每一个tab就是一个iframe,现在遇到的问题就是,如果其中一个或者多个iframe在加载数据,还没加载完,如果这个时候,我再去开一个tab,也是查找数据,可能数据量很大,或者提交数据(其他的iframe可能还在加载数据),这个时候很容易出现浏览器卡死,一直转圈的现象,这个跟你说的这个js阻塞也有关系吧。跟这个js单线程的特性,线程阻塞有多大的关系~~
这个现象很有趣,就和我在上篇文章里谈到我思考无阻塞脚本加载的源起一样,研究这个朋友提供的现象应该也能给我们很大的收获。
首先,这位朋友提到“如果其中一个或者多个iframe在加载数据,还没加载完,如果这个时候,我再去开一个tab,也是查找数据,可能数据量很大,或者提交数据(其他的iframe可能还在加载数据),这个时候很容易出现浏览器卡死,一直转圈的现象”,这个现象让我有个疑问:
首先我们要明确页面使用iframe时候等于一个页面里嵌套一个页面,iframe的src指向的页面其实和父页面是相对独立的,那么一个带了iframe的页面,父页面的加载过程和iframe加载的过程是怎样的关系了,具体点就是父页面是不是要等待iframe页面里所有资源加载完毕后才会触发onload事件,页面的onload事件触发了,也就代表页面的忙指示结束,而忙指示是我们衡量页面是否被阻塞的一个重要标志。
幸运的是有人已经帮我们做了这个实验,下面我来描述下这个实验的方式:
阻塞加载方式即在页面直接写:
<iframe src=”url”></iframe>
Iframe的url指向的页面分为四种类型:
1.url指向一个空文档,空文档不是指页面指向的网页不存在,而是指向网页里只有最基本的html,例如:
<html><head></head><body><p>ddddd</p></body></html>
这时候iframe加载就不会因为内部静态资源而被阻塞。
2.url指向的页面里包含图片;
3.url指向的页面包含外部脚本;
4.url指向的页面包含外部css样式文件。
上面的静态资源,实验设计者都让它们的加载有延时,实验的结果是令人失望的,这些静态资源加载都会阻塞父页面的加载,即iframe的加载延长了忙指示结束的时间。
接下来实验者又做了一个实验,这个时候在父页面里iframe的src设为空,然后使用javascript代码给iframe设定src的值,赋值的时机也是在onload事件之前,而url指向和上个实验一致,结果是在以webkit为内核的safari和chrome下,页面加载明显变快,iframe的加载没有阻塞父页面的加载,但是在其他浏览器下这个结果是令人失望的,阻塞任然存在,甚至比以前还要严重。
Iframe的加载是能阻塞父页面的加载,上面朋友的现象描述里:一个iframe没有加载完毕,用户可能就会选择另外一个iframe,另一个iframe也在加载新页面,这个描述说明了,这个朋友的iframe的src应该都是使用javascript设置,而不是事先填好的,他的场景符合实验二的情况。这里又会产生一个问题,第一个iframe可能是和主页面资源加载同步的,即主页面很有可能还没触发onload事件,为啥用户还能触发iframe的加载操作,按我原来的逻辑,这时候页面应该是不能接收任何响应的,这里我要纠正下这个观点,页面被阻塞不代表页面会停止接收用户的所做的相关操作,例如在ie浏览器,如果页面被阻塞,用户点击了两次按钮,浏览器会认为用户的操作是有效的,会将用户的操作加入到浏览器执行的UI线程里的,这是浏览器对于性能不佳网页的一个妥协做法。
Iframe阻塞页面加载的问题还有更多深层次的原因,首先iframe页面dom元素中加载最慢的,大家可以看看下面这张图:
我们使用dom在页面里创建iframe、a、div、srcipt和style节点,第一栏是创建10个节点,第二栏是创建100个节点,第三栏是创建1000个节点,数值是代表创建的时间,由此可见创建iframe越多,耗时越长,可见iframe本身就是页面性能的瓶颈所在。
在前面文章里我讲到网站为了提升页面资源并行下载的,可以将通用的静态资源放到一个独立的域名下,跨域可以增加页面连接数,使用iframe的页面会有两个url,iframe能达到增加并行下载的目的吗?如果iframe的url和父页面的url不同,能不能增加我没有做过测试,无法回答,但是针对这个朋友的问题,他使用的iframe的url肯定会和父页面在同一个域下面,因为他使用的iframe目的是为了布局而不是为了嵌入其他web的页面,iframe和父页面同域的结果就是并发连接数是不能被增加的,因为iframe是指向一个完整的页面,在加上上面说的iframe其实也会阻塞页面的加载,因此这个场景下就等于浏览器同时加载两个独立页面却要遵守一个页面加载的方案,结果自然是页面会变得更慢。
由于这个朋友的应用是内部的控制台,因此它的网站绝对不会有做那些提升网站性能的优化工作,网站优化里有一条就是:将外部的css文件和js文件缓存,所以这个朋友网站肯定会出现这个问题,第一个iframe还没加载完,用户打开了第二个iframe,这两个iframe使用的外部css文件和js文件可能大部分相同,但是它们在每个iframe里都会被重新加载,而且第一个iframe没有加载完毕,还会阻塞第二个iframe对静态资源的加载,如果开启的iframe越多,阻塞就越加严重,后果自然也是网站越来越慢。
那么我们该怎么帮这个朋友解决他的问题了?最好的方式就是将iframe去掉,使用div来实现选项卡,这么做太残酷,要重构整个前端代码,这个朋友问我的目的就是不想这么干了。
所以要考虑下代价较少的方法,下面是我晚上想到的,希望看了我博客的大牛们也会有更多更好的建议,我想到的如下:
- 这个网站的性能问题源自于iframe使用过多,那么我们应该减少iframe,所以这个朋友可以将页面主页面的布局改为传统布局,即菜单放到页面左边,页面上去掉选项卡,工作区只有一个iframe,这样就只需要修改主页面就行了。不过这样会减少一个用户友好的功能,这个朋友使用选项卡好处就是iframe能有记忆功能,但是左边菜单一个iframe后没点一次菜单就得重新打开页面,不过失去这个功能和性能损失比起来我相信还是性能更加重要。
- 将页面的静态资源添加缓存功能,缓存外部的js文件、css文件还有图片,我不知道这个朋友服务端使用什么语言开发,如果是java的话,生产的web容器应该是tomcat,jboss之流了,这些java的web容器怎么设置静态资源缓存我还真的不清楚,最好还是将静态资源放到apache,ngnix这样的web服务器下,如果使用静态资源服务器最好提供一个和主服务器的域名不一样的访问地址,这样还可以提升浏览器并发下载的连接数。
文章写毕,最后再重申一下希望有大牛看了本人的文章,能告诉我们更多更好的方式。