一个页面,从被请求访问,到用户可以看到页面、操作页面,到最后页面完全加载完毕,中间需要经历一个相当奇幻的过程,这个过程的速度被“web性能师”孜孜不倦、前赴后继的优化。本文讨论的是其中一个优化。 浏览器线程和阻塞虽然大家耳熟能详的一句话是: JavaScript是单线程的。 但是: 浏览器当然不是单线程的。 浏览器的多线程中,有的线程负责加载资源,有的线程负责执行脚本,有的线程负责渲染界面,有的线程负责轮询、监听用户事件。 这些线程,根据浏览器自身特点以及web标准等等,有的会被浏览器特意的阻塞。两个很明显的阻塞就是: 脚本执行时对其他线程的阻塞 和 脚本加载时对其他线程的阻塞 。 这两个阻塞发生在HTML页面初次解析时,它们对性能的影响较大,原因是:
我们先从执行脚本时的阻塞说起。 <!--more--> 执行脚本带来的阻塞众所周知,浏览器中有两个引擎——JavaScript引擎和渲染引擎,它们对应了浏览器的两个线程。这两个引擎各司其职:
在浏览器取得HTML文档并解析HTML的时候,浏览器会: <body> <script> 由于: 脚本执行和渲染DOM的并发可能会引发严重的冲突,所以: JavaScript引擎和渲染引擎所在的两个线程被设计为互斥的!这就意味着:
在执行 解决这个问题的一个经典思路,就是:
把 加载资源带来的阻塞再来说说加载。 加载是浏览器从网络中请求资源(比如图片、样式文件、静态脚本等),将资源进行相应的处理。 与上述说的两个线程不同,对资源进行加载的线程一般不会和上述两个线程互斥。例如图片资源就可以并行下载, 下载的最大并行数量与浏览器的配置有关系 。(这里还有一个知识点,下载的最大并行数指的是从一个主机上下载的最大并行数,如果从多个主机下载资源,这个数量会翻倍,但是由于对DNS的解析也是一个性能优化的点,故而一般策略是: 不应设置超过4个主机,最好只设置2个主机 )。 不会互斥意味着:资源的加载可以和UI渲染、重排,事件响应,或者JavaScript代码的执行的并发进行。 但是操蛋的就是,如果浏览器解析DOM时需要下载脚本资源,那么下载这个资源的线程就是阻塞其他下载线程以及渲染线程,导致渲染速度变慢。
为了解决这个问题,我们依然是将
但是假设该脚本下载的速度较慢,而且多个脚本非并发下载,并且假如多个 故而我们需要无阻塞加载脚本的技术。 无阻塞加载脚本之一——defer将问题暴露出来之后,我们可以根据其阻塞的原因反向想出解决思路。 我们能否将脚本资源文件像图片一样并发的加载而不是让渲染线程挂起等待?道理上是可以的。之所以要让它阻塞等待,是因为担心JavaScript脚本会修改DOM。所以如果我们对其比较放心的话,是不必让渲染线程等待的。
这就是原生的 <script src='..' defer></script>
但是
另一个更加没有得到支持的属性是 无阻塞加载脚本之二——动态脚本元素
所谓的动态脚本元素,就是说
为什么可以做到这样呢?因为 不同于静态脚本元素的解析,动态脚本元素在下载的时候是 不会阻塞渲染线程的 ,也就是实现了并行下载。 <script> var node = document.createElement('script'); node.src = '...'; document.head.appendChild(node); </script>
这个标签可以放到
那么脚本在什么时候执行呢?
答案是 由于资源的大小不同,所以这些脚本的执行将会是 异步的 。
由于脚本的异步执行,那么如何解决脚本之间前后依赖的问题呢?我们自然就会想到回调函数。在脚本加载执行完毕后,会触发该 无阻塞加载脚本之三——XMLHttpRequest这种方法的局限很明显: 无法跨域 。
但是如果是非跨域的脚本,我们可以使用 xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ var node = document.createElement('script'); node.text = xhr.responseText; document.head.appendChild(node); } } 这种方法同样可以异步的加载并执行脚本。 结束上述是我自己的微小的见解。如有错误,欢迎指正。 |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|