资讯专栏INFORMATION COLUMN

前端优化-Javascript篇(2.异步加载脚本)

wpw / 2010人阅读

摘要:下面介绍非阻塞加载脚本技术也就是异步加载。非阻塞加载脚本关于的一篇好文目前所有浏览器都支持属性,但是和中只有在加载外部脚本时才会生效,行内脚本使用是没有作用的。在中,只有外部脚本才会发生阻塞。

  上篇博客说过脚本后置可以使页面更快的加载,可是这样的优化还是有限的,如果脚本需要执行一个耗时的操作,就算后置了它还是会阻塞后续脚本加载和执行并且阻塞整个页面。下面介绍非阻塞加载脚本技术也就是异步加载。

非阻塞加载脚本

1.defer(关于defer的一篇好文)
  目前所有浏览器都支持defer属性,但是Chrome和Firefox中只有在加载外部脚本时defer才会生效,行内脚本使用defer是没有作用的。而IE中不论什么情况,defer都有效。
  defer的作用就是阻止脚本在下载完成后立刻执行,它会让脚本延迟到所有脚本加载执行完成后,在DOMContentLoaded之前执行,通俗的说就是顺序加载延迟执行。虽然都是在DOMContentLoaded之前执行,但是在不同浏览器之间,执行的各种脚本执行的顺序还是不一样的。看下面这个例子:


  
  
    
    
    
    
    
    
    
    
    
  
  
    
      
    
    
    
    
    
    
    
    
  

运行结果如下:

从上面可以看出几个问题:
  首先,IE9以下不支持DOMContentLoaded(后面会说明这个情况)
  其次,验证了上面说的Chrome和Firefox行内脚本不支持defer属性
  最后,defer确实达到了延迟执行的目的,没有阻塞后面脚本的加载和执行。但是耗时的操作还是会阻塞DOMContentLoaded事件,而大多数情况下大家都会把页面初始化的脚本附加在DOMContentLoaded事件上,所以defer方法还是不能很好解决这个问题。

2.Script DOM
  这是最常用也是现在普遍的解决方法。它只需要简单几句话就可以实现脚本的异步加载,并且所有浏览器都支持这个方法。但是在每个浏览器中,执行还是略有不同。看下面这个例子:


  
  
    
    
    
    
    
  
  
    
    
        
  

运行结果如下:

  下面这张图是在ScriptDom脚本后面加入一个耗时的脚本,使得这个脚本执行完成后,保证ScriptDOM的脚本处于可执行状态:

  

结果如下:

运行结果同时也说明了几个问题:
  首先,ScriptDOM不会阻塞后续脚本的执行,根据start和end 的位置可以很容易看出。
  其次,在第二张图的情况下,ScriptDOM和defer同时都可以执行,在不同浏览器中它们的优先级的不一样的。在Firfox和Chrome中,ScriptDOM的优先级比defer低,而在IE中情况则相反。
  最后,通过两种情况的对比发现,在Chrome中ScriptDOM不会阻塞DOMContentLoaded事件但是会阻塞onload事件;在Firefox中ScriptDOM既会阻塞DOMContentLoaded事件也会阻塞onload事件;而在IE中,情况则要根据代码执行情况来决定。如果在DOMContentLoaded事件或者onload事件触发之前,ScriptDOM代码处于可执行状态,那么就会阻塞两个事件;如果在DOMContentLoaded事件或者onload事件触发之前,ScriptDOM代码处于不可执行状态,那么就不会阻塞两个事件。总结的来说就是在Chrome和IE中DOMContentLoaded事件不需要等待ScriptDOM执行,而在Firefox中需要等待ScriptDOM执行。

  通过上面两种方法的对比发现,defer和ScriptDOM都不会阻塞后续脚本的执行。但是相对来说,ScriptDOM在使用上更加灵活而且并不总是阻塞DOMContentLoaded事件,并且ScriptDOM的使用场景主要是在按需加载和模块加载器上,而一般使用这些技术的时候,页面已经处于加载完成的状态,所以对于性能不会有影响。
  

DOMContentLoaded

  上面说到DOMContentLoaded事件,DOMcontentLoaded是现代浏览器才支持的一个事件,万恶的IE从IE9开始才支持这个事件。那么在什么情况下才会触发DOMContentLoaded事件呢?DOMContentLoaded会在浏览器接收到服务器传过来的HTML文档,整个页面DOM结构加载完成并且所有行内脚本和外部脚本执行完成后触发 (通过上面异步脚本的例子可以看出,ScriptDOM异步加载脚本不会阻塞DOMContentLoaded,或者说DOMContentLoaded不需要等待ScriptDOM执行就可以出发) ,它跟onload事件的区别是,DOMContentLoaded事件不需要等待图片,ifram和样式表等资源加载完成就会触发,而onload事件需要等待整个页面都加载完成包括各种资源才会触发。所以对于我们来说DOMContentLoaded是一个更有用的事件,因为只要DOM结构加载完成,我们就可以通过Javasscript来操作页面上的DOM节点。
  但是上面关于DOMContentLoaded事件触发条件的定义只是官方文档的说法,具体情况并不总是这样。
  有时样式表的加载会阻塞脚本的执行从而阻塞DOMContentLoaded事件,这种情况一般出现在样式表后面跟着脚本。也就是说如果把脚本放在样式表后面,那么脚本就必须等到样式表加载完成才能开始执行,这样就会阻塞页面的DOMContentLoaded事件。但是这样做也是有道理的,因为有时候我们的脚本会处理DOM样式方面的东西。
  这种阻塞情况在不同浏览器上表现也会不一样。在IE和Firefox中,不管样式表后面跟着是行内脚本还是外部脚本,都会发生阻塞。在Chrome中,只有外部脚本才会发生阻塞。
  由于IE在IE9以下不支持DOMContentLoaded事件,所以我们需要用一些Hack技术来实现这个功能。分两种情况来实现:
  1.网页不嵌套在iframe中
  在IE中我们可以通过一个方式来判断DOM是否加载完成,就是doScroll方法。如果DOM加载完成,那么我们就可以调用document的doScroll方法,否则就会抛出异常。我们可以利用这个特性不断轮询来做Hack。

    function bindReady(handle){
        //判断是否在iframe中
        try{
            var isFrame = window.frameElement != null ;
        }catch(e){}
        if(document.documentElement.doScroll && !isFrame){
            //轮询是否可以调用doScroll方法
            function tryScroll(){
                try{
                    document.documentElement.doScroll("left");
                    handle() ;
                }catch(e){
                    setTimeout(tryScroll,10) ;
                }
            }
            tryScroll() ;
        }
    }

  2.网页嵌套在iframe中
  如果网页嵌套在iframe中,那么是无法通过doScroll的方法来Hack实现DOMContentLoaded的。我们可以通过另外一种方式来实现---readystatechange,代码如下:

    function bindReady(handle){
        document.onreadystatechange = function(){
            if(document.readyState === "complete" || document.readyState === "loaded"){
                handle() ;
            }
        }
    }

  结合上面的讨论,我们可以得出一个通用的bindReady方法。

//绑定DOMContentLoaded事件,支持绑定多个处理函数
var handleList = [] ;
function onReady(handle){
    //按顺序执行处理函数
    var doHandles = function(){
        var length = handleList.length ;
        for(var i = 0 ; i < length ; i ++){
            handleList[i]() ;
        }
    }
    if(handleList.length == 0){
        //在还没有处理函数时,把doHandles注册到ready上,这样后面加入的处理函数就可以一并执行
        bindReady(doHandles) ;
    }
    //把处理函数加入到函数列表中
    handleList.push(handle) ;
}
function bindReady(handle){
    var called = false ;
    var ready = function(){
        //防止重复调用
        if(!called){
            called = true ;
            handle() ;
        }
    }
    if(document.addEventListener){
        //支持DOMcontentLoaded
        document.addEventListener("DOMContentLoaded",ready,false);
    }else if(document.attachEvent){
        //IE
        try{
            var isFrame = window.frameElement != null ;
        }catch(e){}
        //网页不在iframe中
        if(document.documentElement.doScroll && !isFrame){
            function tryScroll(){
                try{
                    document.documentElement.doScroll("left") ;
                    ready() ;
                }catch(e){
                    setTimeout(tryScroll,10) ;
                }
            }
            tryScroll() ;
        }else{
            //网页在iframe中
            document.onreadystatechange = function(){
                if(document.readyState === "complete" || document.readyState === "loaded"){
                    ready() ;
                }
            }
        }
    }
    //老式浏览器不支持上面两种事件
    if(window.addEventListener){
        window.addEventListener("load",ready,false) ;
    }else if(window.attachEvent){
        window.attachEvent("onload",ready) ;
    }else{
        //允许绑定多个处理函数
        var fn = window.onload ;
        window.onload = function(){
            fn && fn() ;
            ready() ;
        }
    }
}
说在最后

  说了这么多,虽然通过脚本后置和异步加载可以降低脚本加载对页面的影响,但是就算是实现了异步加载,但是由于浏览器的脚本解析的单线程的,所以脚本执行的时候仍然会阻塞整个页面(当然除了使用Web Worker),这时候用户是无法完成正常交互的,所以要想真正彻底的优化页面加载,还需要从代码的优化开始。从下一篇开始,我会分享关于这方面的学习。

最后,安利下我的个人博客,欢迎访问: http://bin-playground.top

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78163.html

相关文章

  • 前端性能优化之Performance神器

    摘要:需要注意的一点是,面板下的功能,是对于细节中的细节进行的优化。我们可以很清晰明了得分析按照活动,目录,域,子域,和进行分组的前端性能。个人理解的话,前者类似事件冒泡,后者类似事件捕获。同学在点我达,他们正在筹划改组成大前端团队。   对Chrome控制台有一定的了解的朋友都在知道,Network面板会包括很多网络请求方面的东西,包括Http相关的Request信息,Response信息...

    qujian 评论0 收藏0
  • 浏览器前端优化

    摘要:幸运的是,浏览器行为的基础原理是相当稳定而且文档齐全的,并且在相当长一段时间内肯定不会发生显著变化。浏览器有种称为预加载扫描器的东西,它会扫描的脚本,并开始预加载脚本,不过脚本只会在先前的节点已经构建完成后,才会依次执行。 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/2847原文:https://hackernoon.com/opt...

    yimo 评论0 收藏0
  • 前端每周清单半年盘点之 JavaScript

    摘要:前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。背后的故事本文是对于年之间世界发生的大事件的详细介绍,阐述了从提出到角力到流产的前世今生。 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热点、开发教程、工程实践、深度阅读、开源项目、巅峰人生等栏目。欢迎...

    Vixb 评论0 收藏0
  • 前端优化-Javascript(1.脚本放在底部)

    摘要:从本篇博客开始,我会跟大家分享下我关于前端优化方面的学习,由于时间原因每篇博客只能分享一小点内容,一点点深入前端优化的细节。在前端优化这个问题上,最被大家熟知的应该就是雅虎前端优化条军规以及雅虎前端优化条规则。   从本篇博客开始,我会跟大家分享下我关于前端优化方面的学习,由于时间原因每篇博客只能分享一小点内容,一点点深入前端优化的细节。  做过前端的人都知道,前端优化是一个永远都不会...

    JerryC 评论0 收藏0
  • 前端性能优化之--页面渲染优化全面解析

    摘要:下面我们撇开网络方面的优化,只分析静态资源方面的优化。不过,也会阻止的构建和延缓网页渲染。未优化正常加载优化后异步加载根据上面的分析,我们可以清楚的认识到,非必要优先加载的,选择异步加载是最优选择。 为什么做优化 经典问题:白屏时间过长,用户体验差产生的原因:网络问题、关键渲染路径(CRP)问题 怎么做优化 如何做好优化呢,网上随便一搜,就有很多优化总结,无非就是网络优化、静态资源(h...

    MadPecker 评论0 收藏0

发表评论

0条评论

wpw

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<