这篇笔记的内容主要涉及js的脚本位置,如何加载js脚本和脚本文件执行的问题,按照自己的理解结合高性能JavaScript整理出来的
javascript是解释性代码,解释性代码需要经历转化成计算机指令的过程,这个过程就会带来一定的性能损耗,所以在js中做性能的优化是必须的
javascript的阻塞特性:浏览器在执行js代码的时候,不能做其他的任何事情,因为浏览器使用单一的进程来处理用户界面的刷新和javascript的脚本执行,也就是说什么时候执行js脚本影响着用户对页面的使用体验(之所以js会阻塞页面的解析和渲染,是因为无法预期js时候会对页面进行修改,所以会先执行完js代码在继续解析和渲染界面,无论是外链的js文件或者是内联的js文件的)
基于以上的原因,js文件位置决定这用户的体验并且通过js文件的优化能尽可能的提高页面的性能
<head> <meta charset="UTF-8"> <title>Document</title> <script type="text/javascript" src="a.js"></script> <script type="text/javascript" src="b.js"></script> <link rel="stylesheet" type="text/css" href="style.css" /> </head>
上面这种模式我们将js文件放置在head中,这样的位置是存在问题的
(1)首先js的阻塞特性会导致必须等待这两个文件下载和执行,页面才会渲染,会出现空白,用户体验不好
(2)浏览器在解析body标签之前不会渲染页面的任何部分,也就是在a.js 和 b.js 在执行的过程中不存在页面的dom树,这个时候对页面进行操作就会出错
/*虽然现代的浏览器可以实现js的并行下载,但是js的下载过程中还是会阻塞其他资源的下载,例如图片等 外链的CSS文件本身已经是并行下载的*/
所以推荐的js脚本放置位置是下面这样的形式
<body> <script type="text/javascript" src="a.js"></script> <script type="text/javascript" src="b.js"></script> </body>
这样的放置模式在下载脚本文件和执行脚本文件的时候,页面的大部分内容已经显示给用户,也就是放置在body的最底部
组织脚本
script标签会阻塞页面的渲染,从提高页面的性能的角度考虑,就是如何尽可能的减少script标签去组织脚本,由于现在js文件的模块化和功能化越来越清晰,文件的数量有的时候很难去实质的减少,一个并不是特别好的方案,就是简单的合并两个js文件,因为减少了http请求这样的方式会比单纯的下载两个之前独立的js文件要快,但是这样也存在一定的问题
(1)破坏了js文件的模块性,两个不同功能的模块糅合在一起了
(2)在服务端我们需要增加更多的js文件,占据着服务器的资源
我们可以通过一些静态打包工具或者类似雅虎提供的合并处理器通过它们的CDN来实现一个url来加载两个js文件
/*不要将内联脚本放置在外链样式表的后面,这样会导致页面阻塞去等待样式表去下载(为了在js文件执行的时候获取到最精准的样式信息)*/
无阻塞脚本
通过上面合并url或者合并js文件并不能很好地提高的页面的性能,所以提出了无阻塞脚本,就是在页面加载完成后才加载js代码,也就是在相应window.onload事件触发后才去下载脚本(仔细理解无阻塞脚本就是这个js文件的下载不会阻塞页面其他元素的下载) 有几种方式可以实现上面的要求
(1)延时脚本defer
defer属性在js文件不会修改文档的时候可以使用,因为js文件不会修改文档,就不需要等待js的执行去停止页面的渲染 等待页面完成后执行(无位置需求)
ansyc 属性 js文件下载完成后自动执行(body底部 下载执行的时候需要页面的元素准备完毕)
可以通过上面这两种方式实现无阻塞脚本
举一个defer的例子
/*我测试了chrome下和IE下都支持了defer属性 但是当js文件是内联的时候 defer就会失效 */
<body> <script type="text/javascript" src="a.js" defer></script> <script type="text/javascript" defer> console.log(1); </script> <input type="button" value="test" id="btn" /> </body>
a.js中的内容如下
var btn = document.getElementById("btn");
console.log(btn);
通过查看控制台我们发现a.js的确延时执行了,也就是在页面window.load 之前执行了a.js文件 但是内联的js的文件并没有延时
(2)动态脚本元素 script元素与其他元素一样可以动态的创建script元素,并且这种方式文件下载和执行的过程不会阻塞其他的进程,并且这种方式添加的脚本文件会在下载完成后立即执行,但是当你添加的这个脚本是一个提供接口的脚本的时候就需要获取一些信息来确认当前这个接口是否可以然后在进行后面的操作 IE下可以通过onreadystatechange事件 通过script的readyState状态来获取脚本完成时的状态 标准浏览器是通过onload事件来判断添加的js文件的状态
可以通过下面这个函数来实现动态的加载js文件
function loadScript(url,callback) { var script = document.createElement("script"); script.type = "text/javascript"; if(script.readyState) { script.onreadystatechange = function(){ if(script.readyState == "loaded" || script.readyState == "complete") { script.onreadystatechange = null; callback(); } } } else { window.onload = function() { callback(); } } script.src = url; document.getElementsByTagName("head")[0].appendChild(script); /*添加到页面的时候开始下载,下载的过程执行都不会阻塞其他进程*/ console.log(1);/*1会在新添加的script执行前输出*/ }
如果需要脚本之间按照特定的顺序下载执行可以按照回调的方式一个一个的加载js文件
loadScript("a.js",function(){ loadScript("b.js",function(){}); });
(3)XMLHttpRequest脚本注入 也就是通过xhr对象去下载脚本文件 这种情况的优势是你可以下载到代码但不立即执行,等到你准备好的时候在去执行相应的代码
var xhr = new XMLHttpRequest(); xhr.open("get","a.js",true); /*true表示异步*/ xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { var script = document.createElement("script"); script.type = "text/javascript"; script.text = xhr.responseText; document.body.appendChild(script); } } } xhr.send(null);
推荐的方式: 通过阻塞的方式加载一小部分代码,例如loadSript 然后在去动态的加载剩余的代码
<script type="text/javascript" src="load.js"></script> <script type="text/javascript"> loadScript("b.js",function(){ console.log("OK"); }); </script>
参考高性能javascript