分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 网页技术

js加载优化

发布时间:2023-09-06 01:44责任编辑:沈小雨关键词:js
在js引擎部分,我们可以了解到,当渲染引擎解析到script标签时,会将控制权给JS引擎,如果script加载的是外部资源,则需要等待下载完后才能执行。 所以可以对其进行很多优化工作。

放置在body底部

为了让渲染引擎能够及早的将DOM树给渲染出来,我们需要将script放在body的底部,让页面尽早脱离白屏的现象,即会提早触发DOMContentLoaded事件. 但是由于在IOS Safari, Android browser以及IOS webview里面即使你把js脚本放到body尾部,结果还是一样。 所以这里需要另外的操作来对js文件加载进行优化.

defer加载

这是HTML4中定义的一个script属性,它用来表示的是,当渲染引擎遇到script的时候,如果script引用的是外部资源,则会暂时挂起,并进行加载。 渲染引擎继续解析下面的HTML文档,解析完时,则会执行script里面的脚本。

<scriptsrc="outside.js"defer></script>

他的支持度是<=IE9的.
并且,他的执行顺序,是严格依赖的,即:

<scriptsrc="outside1.js"defer></script><scriptsrc="outside2.js"defer></script>

当页面解析完后,他便会开始按照顺序执行 outside1 和 outside2文件。
如果你在IE9以下使用defer的话,可能会遇到 它们两个不是顺序执行的,这里需要一个hack进行处理,即在两个中间加上一个空的script标签

<scriptsrc="outside1.js"defer></script><script></script>//hack<scriptsrc="outside2.js"defer></script>

但是,如果你将defer属性用在inline的script脚本里面,在Chrome和FF下是没有效果的。
即:

<scripttype="text/javascript"defer="defer">//没有效果console.log("deferdoesn'tmakesense");</script>

async加载

async是H5新定义的一个script 属性。 他是另外一种js的加载模式。

  1. 渲染引擎解析文件,如果遇到script(with async)

  2. 继续解析剩下的文件,同时并行加载script的外部资源

  3. 当script加载完成之后,则浏览器暂停解析文档,将权限交给JS引擎,指定加载的脚本。

  4. 执行完后,则恢复浏览器解析脚本

可以看出async也可以解决 阻塞加载 这个问题。不过,async执行的时候是异步执行,造成的是,执行文件的顺序不一致。即:

<scriptsrc="outside1.js"async></script><scriptsrc="outside2.js"async></script>

这时,谁先加载完,就先执行谁。所以,一般依赖文件就不应该使用async而应该使用defer.
defer的兼容性比较差,为IE9+,不过一般是在移动端使用,也就不存在这个problem了。
其实,defer和async的原理图,如图一样。(包括放在head中的script标签)

脚本异步

脚本异步是一些异步加载库(比如require)使用的基本加载原理. 直接上代码:

functionasyncAdd(src){varscript=document.createElement('script');script.src=src;document.head.appendChild(script);}//加载js文件asyncAdd("test.js");

这时候,可以异步加载文件,不会造成阻塞的效果.
但是,这样加载的js文件是无序的,无法正常加载依赖文件。
如果你想要js文件按照你自定义的顺序执行,则要将async设置为false. 但是会阻塞其它文件的加载

varasyncAdd=(function(){varhead=document.head,script;returnfunction(src){script=document.createElement('script');script.src=src;script.async=false;document.head.appendChild(script);}})();//加载文件asyncAdd("first.js");asyncAdd("second.js");//或者简便一点["first.js","second.js"].forEach((src)=>{async(src);});

但是,使用脚本异步加载的话,需要等待css文件加载完后,才开始进行加载,不能充分利用浏览器的并发加载优势。而使用静态文本加载async或者defer则不会出现这个问题。
使用脚本异步加载时,只能等待css加载完后才会加载

使用静态的async加载时,css和js会并发一起加载

(from 妙净)

关于这三种如何取舍,那就主要看leader给我们目标是什么,是兼容IE8,9还是手机端,还是桌面浏览器,或者两两组合。
但是对于单独使用某一个技能的场景,使用时需要注意一些tips。
js文件放置位置应该放置到body末尾
如果使用async的话,最后加上defer以求向下兼容

<scriptsrc="test.js"asyncdefer></script>//如果两者都支持,async会默认覆盖掉defer//如果只支持一个,则执行对应的即可

通常,我们使用的加载都是defer加载(因为很强的依赖关系).
但,上面的简单js文件依赖加载只针对于,依赖关系不强,或者说,相互关联性不强的js文件。先在js模块化思想 已经成为主流, 如果这样手动添加defer或者async是没有太大的实际意义的。
原因就在于, 好复杂~
所以,才有了webpack,requireJS等模块打包工具。这也是给我们在性能和结构上寻找一个平衡点的尝试。
这里也给大家安利一些建议:
业务逻辑代码使用模块化书写, 测试代码或者监听代码使用async,或者defer填充。 这也是比较好的实践。

深入脚本异步加载

最简单的脚本异步就是在head里添加一个script标签.

varasyncAdd=(function(){varhead=document.head,script;returnfunction(src){script=document.createElement('script');script.async=false;document.head.appendChild(script);}})();asyncAdd("test.js");//异步加载文档

这样写,其实还不如,直接加async. 这样简单的异步加载,是不能满足我们模块化书写的庞大业务逻辑的。 这里,我们将一步一步的优化我们的代码,实现,异步js文件加载的模块化.

串行加载js文件

对上述简单js异步脚本的升级版就是使用串行方式,加载js脚本。首先,我们需要了解一下,DOMreadyState和onload事件,这里先安利一下Nicholas大神 推荐的一份检测onload的脚本:

functionloadScript(url,callback){varscript=document.createElement("script")script.type="text/javascript";if(script.readyState){//IEscript.onreadystatechange=function(){if(script.readyState=="loaded"||script.readyState=="complete"){script.onreadystatechange=null;//解除引用callback();}};}else{//Othersscript.onload=function(){callback();};}script.src=url;document.body.appendChild(script);}

但从IE11开始,已经支持onload事件, 不过,现在这份代码的价值还是非常大的, 目前主流兼容IE8+。
当然,我们可以使用loadScript中进行回调加载.

loadScript("test1.js",loadScript("test2.js",loadScript("test3.js")));

不过,这简直就是没人性的写法。 所以,这里我们可以进行优化一下。我们可以使用以前的模式,进行重构,这里我选择命令模式和链式调用。
直接贴代码吧:

varloadJs=(function(){varscript=document.createElement('script');if(script.readyState){returnfunction(url,cb){script=document.createElement('script');script.src=url;document.body.appendChild(script);script.onreadystatechange=function(){if(script.readyState=="loaded"||script.readyState=="complete"){script.onreadystatechange=null;//解除引用cb();}};}}else{returnfunction(url,cb){script=document.createElement('script');script.src=url;document.body.appendChild(script);script.onload=function(){cb();};}}})();//测试用例:commandJs.add("test.js",[test.js,test1.js]).exe();//或者commandJs.add("test.js").add("test1.js").add([test1.js,test2,js]).exe();varcommandJs=(function(){vargroup=[],len=0;//类型检测//数组varisArray=function(para){return(parainstanceofArray);}//String类型varisString=function(para){returnObject.prototype.toString.call(para)==="[objectString]";}//集合检测varcorrectType=function(para){returnisString(para)||isArray(para);}//添加src内容varadd=function(){for(vari=0,js;js=arguments[i++];){if(!correctType(js)){thrownewError(`the${i}thjsfile'stypeisnotcorrect`);}group.push(js);}returnthis;}varisFinish=function(){len--;if(len===0){exe();//开始加载下一组js文件}}//并行加载js文件varloadArray=function(urls){urls.forEach((url)=>{loadJs(url,(function(){isFinish();//判断是否执行完全}).bind(this));});}varexe=function(){if(group.length===0)return;//遍历完所有的urls时,退出执行varjs=group.shift();if(isArray(js)){len=js.length;loadArray(js);}else{len=1;loadArray([js]);}returnthis;}return{exe,add}})();

串行执行的测试结果:

commandJs.add('./js/loader01.js').add('./js/loader02.js').exe();//或者commandJs.add('./js/loader01.js','./js/loader02.js').exe();//这两种写法都是可以的

最后的结果是:

ok~ 可以通过,这样可以自定义加载很多依赖文件。 但是,造成结果是,时间成本耗费太大。 有时候, 一个主文件的main 有很多依赖js模块, 那么我们考虑一下,能否把这些js模块并行加载进来呢?

    知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved