浏览器面试——js加载
浏览器的主要功能是将用户选择的web资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是HTML,也包括PDF、image及其他格式。用户用URI来指定所请求资源的位置,在网络一章有更多讨论。
HTML和CSS规范中规定了浏览器解释html文档的方式,由W3C组织对这些规范进行维护。
浏览器的主要构成(High Level Structure)
1. 用户界面 - 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分。
2. 浏览器引擎 - 用来查询及操作渲染引擎的接口。
3. 渲染引擎 - 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来。
4. 网络 - 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作。
5. UI后端 - 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口。
6. JS解释器 - 用来解释执行JS代码。
7. 数据存储 - 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术
渲染引擎:
渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。
Firefox、Chrome和Safari是基于两种渲染引擎构建的,Firefox使用Geoko——Mozilla自主研发的渲染引擎,Safari和Chrome都使用webkit。
渲染引擎首先通过网络获得所请求文档的内容,下面是渲染引擎在取得内容之后的基本流程:
解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树
Render树构建好了之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。
再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。
值得注意的是,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
Webkit中元素的定位称为布局,而Gecko中称为回流。
hmtl不能被一般的自顶向下或自底向上的解析器所解析。
原因是:
1. 这门语言本身的宽容特性
2. 浏览器对一些常见的非法html有容错机制
3. 解析过程是往复的,通常源码不会在解析过程中发生改变,但在html中,脚本标签包含的“document.write”可能添加标签,这说明在解析过程中实际上修改了输入。
浏览器为html定制了专属的解析器。
浏览器对js脚本的解析
script标签每次出现都会霸道的让页面等待脚本的解析和执行,同样,当使用script的src属性加载页面时,浏览器必须先花时间下载外链文件中的代码,然后解析并执行。在这个过程中,页面渲染和用户交互是完全被阻塞的。
浏览器之所以产生这样的行为,是因为当前HTML页面无从知晓JS的动作:JS可能会向document里添加内容、引入其它元素、甚至关闭标签。
所以浏览器会先(下载和)执行JS代码,然后才解析和渲染页面。
js加载优化
想要使页面得到更快的渲染,一是优化脚本的执行速度,例如使用观察者模式减少初始化代码的执行数量,这并不在这篇文章的阐述范围之内。请牢记,默认状态下,所有的脚本必须被执行后,页面才会开始渲染。即在浏览器解析页面之前,须先读取并执行脚本。我们现在试着对脚本的下载过程进行优化。
现代浏览器都允许并行下载JS文件,但JS文件的下载过程仍然会阻塞其他比如图片资源的下载。尽管JS文件的下载并不会相互影响,但是浏览器会等待全部的JS代码下载完成才执行之。
1. 优化JS的首要规则:将脚本放在底部
这样做可以防止脚本代码的下载与执行阻塞页面其它资源,比如图片的下载(下载往往需要更多的时间),以尽量减少对整个页面下载的影响。
如果把脚本文件放在头部,那么需要等到所有脚本下载并执行完毕后才能下载图片等其他资源,这是多么的可怕啊。但如果我们把脚本放在body标签的尾部,则可以是其它资源的下载和脚本的下载与执行并发进行,能够大大加快页面的下载速度。
2. 减少script标签的数量,减少延时
3. 不要把内嵌脚本紧跟在link标签后面
这样做会导致页面阻塞去等待样式表的下载,因为需要确保内嵌脚本在执行时能获得最准确的样式信息。
4. 减少外链脚本文件的数量——将多个外链JS文件合并成一个以减少HTTP开销
无阻塞的脚本
无阻塞脚本意味着脚本的下载和执行不阻塞其他资源的下载和页面的解析。
可以通过设置script标签的defer和async属性使其拥有不阻塞的特性,它们仅对外部链接的script有效。
带有async或者defer的script都会立刻下载并不阻塞页面解析,它们的不同之处在于script执行的时机。
(1)defer:
1、如果有多个设置了defer的script标签存在,则会按照顺序执行所有的script;
2、defer 与 DOMContentLoaded
如果 script 标签中包含 defer,那么这一块脚本将不会影响 HTML 文档的解析,而是等到 HTML 解析完成后才会执行。而 DOMContentLoaded 只有在 defer 脚本执行结束后才会被触发。 所以这意味着什么呢?HTML 文档解析不受影响,等 DOM 构建完成之后 defer 脚本执行,但脚本执行之前需要等待 CSSOM 构建完成。在 DOM、CSSOM 构建完毕,defer 脚本执行完成之后,DOMContentLoaded 事件触发。
contextload改为DOMContentLoad(2)async:
1、async的执行,并不会按着script在页面中的顺序来执行,而是谁先加载完谁执行。
2、async 与 DOMContentLoaded
如果 script 标签中包含 async,则 HTML 文档构建不受影响,解析完毕后,DOMContentLoaded 触发,而不需要等待 async 脚本执行、样式表加载等等。
(3)defer和async的区别:
(1)defer 和 async 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
(2)它俩的差别在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的
(3)关于 defer,此图未尽之处在于它是按照加载顺序执行脚本的,这一点要善加利用
(4)async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行
仔细想想,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics