分类目录归档:FrontEnd

Javascript 的同步加载和执行

现在的前端技术中,javascript(以下简称 js)异步加载的方案已经很多。在研究异步加载前,我们对同步加载了解的够多么?

同步加载大概意思为:浏览器解析文档的过程中,遇到加载 js 文件的代码,暂停文档解析,执行加载的代码,等待文件下载,等待文件中的代码执行完成,然后继续文档解析。

所以异步加载就是:浏览器解析文档过程中,遇到加载 js 文件的代码,暂停文档解析,执行加载代码,然后继续文档解析。不等待文件的下载。

同步加载的几种情况:

1、包含代码的 <script></script>  标签。虽然没有网络请求,但也可以当做是加载了文档内部的一段代码。标签内的代码按顺序执行,如果出现多个标签对,按标签对的出现顺序执行。

2、设置 src 属性的 <script></script>  标签。按标签的出现顺序执行。老的浏览器中,加载也是按顺序的:一个文件下载完成后,才开始下载下一个文件。较新的浏览器中(IE8+ 、FireFox3.5+ 、Chrome4+ 、Safari4+),为了减小请求时间以优化体验,下载可以是并行的,但是执行顺序还是按照标签出现的顺序。

3、在 <script></script>  标签中的代码,或者通过 src 引用的文件中,使用 document.write()  来写入的 js 代码。代码中也有上面 1 和 2 两种情况,执行顺序也是按照出现的先后。在 IE 中有个例外,在 <script></script>  标签中,假设有这种 document.write('<script src=""><\/script>')  加载文件的代码,则文件会放到 </script>  标签闭合处才执行。

同步加载的执行顺序:

因为核心 js 语言并不包含任何线程机制,并且浏览器端 js 也没增加任何线程机制,所以浏览器端 js 是单线程的(或者像单线程一样工作)。因此 js 代码只能按顺序执行,就算浏览器为了体验可以并行下载多个 js 文件,但就算个别文件下载的特别快,也得按顺序被执行。

同步加载的应用:

1、我们平时的开发中用的基本上都是同步加载。比如先加载一个 jQuery 库文件,接下来就可以使用 $ 符号了。原理上,是因为 js 代码的运行环境是在一个全局对象里,代码声明全局变量和函数,实际是在修改全局对象的属性,这些修改会被保持着,所以下一段代码可以使用上一段代码声明的全局变量和函数。

2、常用于跨域请求数据的 JSONP。我们在本站声明一个回调函数,然后向外站请求一个 js 文件,文件中的代码是用外站的数据对本站函数进行调用,就实现了使用外站数据的目的。虽然函数是本站声明的,数据是外站的,外站调用本站函数,但是把它理解为同步加载和执行就简单了。

3、前端优化中的 “脚本尽量放在页面底部” 这条规则,也很好解释。因为是同步加载和执行,js 的加载和执行都会阻塞页面的解析,如果时间长了,体验很不好,所以将脚本尽量放在底部,影响最小。

几个容易混淆的问题:

1、将 document.write('<script src=""><\/script>')  这类代码,移到 window.onload  事件中,算异步加载么?

还是同步加载。这样改其实是改变了这段加载代码的执行时机,推迟到文件加载完成时才执行,但并没有改变加载的方式。

2、同步加载第 3 中情况中,如果嵌套了很多层,执行顺序是怎样的?比如,在被加载的 js 文件中,又用 document.write()  来加载了一个 js 文件。

还是按出现顺序来执行。根据单线程的模型,只能逐个解析和执行。

 

最后站在浏览器的角度总结下:因为浏览器的 js 是单线程的,而且 js 都运行在 window 这个全局对象中,所以必须逐个解析、加载和执行 <script></script>  标签中包含的代码或文件;为了保证这种顺序,需要同步加载 js 文件。

在 GitHub 上放了点测试代码。

 

参考资料:

Coupling asynchronous scripts
Browser script loading roundup
Positioning Inline Scripts
Javascript 文件的同步加载与异步加载
JavaScript中的对象动态加载技术
Javascript在页面加载时的执行顺序

前端优化基础规则

rocket

前几天着重解释了几个点,其他的就列出来,做个了解,等有更多理解时再做针对性的阐述。

1、最重要的规则:减少 HTTP 请求数量。减少组件数量,由此减少 HTTP 请求的数量,这是改善响应时间的最简单的途径。

具体方法有:
图片地图:在一个图片上关联多个 URL(做多个超链接),比如每个导航项都有个图片和链接,将这些图片合并,在上面做图片地图,节省请求;
Css Sprites:也可以合并图片,但更为灵活;
内联图片:就是使用 data: URL 的模式,直接将图片数据包含在页面中,数据需要经过 base64 编码;
合并脚本和样式表;

2、使用内容发布网络:将内容发布到离用户更近的位置,减少 HTTP 响应时间。现在国内 CDN 服务提供商已经有很多了。

3、添加 Expires 头,利用浏览器缓存:首次访问的响应时间并不是唯一需要考虑的,页面初访者会进行很多的 HTTP 请求,但通过使用长久的 Expires 头,使很多组件都可以缓存在浏览器端。但是,要确保用户能获取组件的最新版本,就需要在 HTML 页面中修改组件的文件名,需要一些技巧。

4、压缩组件:将组件压缩可以减小体积,所以可以提高 HTTP 响应时间。浏览器请求时会声明支持的压缩格式,服务器端如果压缩了则会用头信息通知浏览器。gzip 是目前支持最广,最理想的压缩方式。文本文档都可以压缩,图片和 PDF 不应该压缩(本来就压缩过了)。压缩的成本在于服务器压缩文件花费的 CPU 资源,还有客户端对文件的解压缩,经验上通常对大于 1KB 或 2KB 的文件进行压缩。

5、样式表放头部,脚本放底部:这个前两天的文章已经阐述过了,略。

6、避免 CSS 表达式:IE 支持在 CSS 中插入 Javascript 表达式,如果页面中有 CSS 表达式,加载页面、各种事件发生时(改变大小、滚动、鼠标移动等)CSS 表达式可能都会被重新求值,在页面上来回移动鼠标,都可以轻易产生上万次的求值。所以,太危险了。

7、使用外部的 Javascript 和 CSS:纯粹而言,内联的 Javascript 和 CSS 快,但很多页面都用了相同的 Javascript 和 CSS,那么外部文件可以提高这些组件的重用率。两全其美的解决方法,可以在页面加载完成后,再去加载其他页面的组件。

8、减少 DNS 查询:因为 DNS 解析也是要花时间的,而且浏览器对缓存的 DNS 记录的数量也有限制。

9、精简 Javascript:原理上和 gzip 压缩类似,都是减小文件。Javascript 还用到“混淆”的方法,修改替换变量名,将代码进一步缩小,但有几个缺陷:会引入更多错误、难维护、难调试,所以,如果已经用了 gzip 压缩,只要精简掉空格啥的就够了。

10、避免重定向:重定向会增加响应时间,如果是网页文档被重定向了,重定向过程中网页还是白屏,体验很差。重定向最常用的是 301 和 302 这两个类型。

11、移除重复脚本:团队大了或脚本多了会搞出相同脚本加载了多次,这个不太常见,如果脚本多了,就要有管理的措施。

12、配置 Etag:ETag 指实体标签(Entity Tag),是 Web 服务器和浏览器用于确认缓存组件的有效性的一种机制。比起其他条件请求只能用时间来判断缓存的有效性,ETag 则是用组件的某些属性来构造的(比如 Apache 1.3 和 2.x 的 ETag 格式是 inode-size-timestamp),所以它在判断缓存有效性时更灵活。但是在分布式环境下 ETag 会带来问题,一般会修改默认的 ETag 构造格式。

13、缓存 Ajax 请求:有条件的缓存一些 Ajax 请求,可以提升体验。

最后,这些规则其实只是实践,最基础的知识,是 HTTP 协议规范、浏览器的工作原理、网络原理,掌握好这些,才能深刻理解这些实践,并创造更佳实践。

 

封面图片来自花瓣网,伯仲舍设计。我觉得上面的规则像一大堆零件,还涉及其他一堆零件,找“零件”时,发现了这些用零件设计出来的机械甲壳虫。

为什么样式表在顶部加载,脚本在底部加载

bike

为了用户体验,浏览器尽量快地显示内容,所以我们能看到比较大的网站的页面是逐步加载的。逐步加载的好处是,在加载过程中,它让用户知道系统没有崩溃,并告诉用户大概还要等多久,最后一点,它能给用户提供一些可以看的东西。

文档头部的样式表没加载完成时,会阻碍浏览器对内容的逐步呈现。因为当样式表没加载完时浏览器为了避免以后重新绘制页面中的元素,会停止内容的逐步呈现。

做个实验。在网页的头部用 <link>  标签加载一个样式表,样式表的地址是个 php 脚本,脚本中正常输出样式表内容前,调用 PHP 的 sleep()  函数,睡个几秒。刷新网页看效果,在样式表没加载好之前,网页一直是白屏。

白屏是一种非常不好的用户体验。如果将样式表放在网页底部,在 IE 较低版本中的一些情况下(新窗口下打开、重新加载、作为主页时被打开)就会造成白屏。白屏只是 IE 浏览器的行为,其他浏览器没这么处理。

那么,将样式表放在网页底部,在其他浏览器下、IE 的其他情况下的表现是怎样的?浏览器会选择逐步呈现内容,但是会出现 FOUC(Flash Of Unstyled Content,无样式内容的闪烁) 的问题。

当页面逐步加载时,文字首先显示,然后是图片。最后样式表正确地下载并解析时,已经呈现的文字和图片要用新的样式重绘了。页面在重绘时,颜色、大小等都可能发生变化,看上去就像在闪烁一样,体验也相当不好。上面那个实验,只要把 <link>  标签挪到页面底部就能看到效果。

白屏 和 FOUC 只是浏览器在尝试修复前端工程师所犯错误(将样式表放在文档比较靠后的位置)所做的处理方式的选择。选择 FOUC,在如果闪烁不严重,那用户体验就不会怎么差;选择白屏,根本没有获得好的体验的机会。

避免以上问题的方法就是:将样式表放在网页顶部加载。

下面是脚本的问题。

网页文档下载完成后,紧接着就开始下载网页中的组件了(图片、样式表文件、脚本),为了更快的显示网页内容,浏览器会并行下载这些组件。但是下载脚本时,并行下载是被禁用的,也就是说,下载脚本时,浏览器不会启动其他的下载。其中一个原因是,脚本可能使用 document.write  来修改页面的内容,因此浏览器会等待,以确保页面能正确的布局。

将脚本放在页面顶部,会有两种影响:脚本会阻塞它后面内容的呈现,脚本会阻塞它后面组件的下载。修改上面的实验,将加载样式表的地方改成加载脚本,如果脚本在顶部,页面就会出现白屏,而且其他组件还没开始下载。

所以,将脚本放在底部加载,即使请求时间较长,对页面的影响也很小。体验最佳。

 

参考自《High Performance Web Sites》,作者编写了很多实验放在他的网站上:http://stevesouders.com/hpws/

图片来自网络。网页也是组装出来的。