该面试题主要考察的是程序的性能方面。性能优化的核心思想就是快,常见的优化手段有预先准备数据(如缓存),按需获取,分段、异步获取等
由于JavaScript的阻塞特性(单线程运行),在每一个<script>
出现的时候,无论是内嵌还是外链的方式,它都会让页面等待脚本的加载解析和执行
(1)<script>
标签放在页面的中时,由于js的阻塞问题,当加载到<script>
时, 其后面的内容将会被挂起等待,直到该js加载、执行完毕,这样用户打开该界面的时候,界面内容会明显被延迟,我们就会看到一个空白的页面闪过,这种体验是明显不好的
(2)优化加载顺序:尽量的让内容和样式先展示出来,即将js文件放在<body>
最后,以此来优化用户体验。
存在的问题:只有在所有HTML DOM加载完成后才开始脚本的加载/解析过程。对于有大量js代码的大型网站,可能会带来显著的性能损耗。
注意:不应该将页面正常加载需要依赖的javascript代码放在这里,而应该将JavaScript代码分成两组。一组是因页面需要而立即加载的javascript代码,另外一组是在页面加载后进行操作的javascript代码(例如添加click事件或其他东西)。这些需等到页面加载后再执行的JavaScript代码,应放在一个外部文件,然后再引进来。
(3)减少请求次数: 页面加载的过程中,最耗时间的不是js本身的加载和执行,相比之下,每一次去后端获取资源,客户端与后台建立链接才是最耗时的(即Http三次握手)。减少HTTP请求,需要文件的精简与压缩
常用的解析型的压缩工具有三种:
- YUI Compressor:去除代码中的注释和额外的空格并且会用单个或者两个字符去代替局部变量以节省更多的字节。但默认会关闭对可能导致错误的替换,例如with或者eval();
- Closure Complier:去除注释和额外的空格并进行变量替换,而且会分析你的代码进行相应的优化,比如他会删除你定义了但未使用的变量,也会把只使用了一次的变量变成内联函数
- UglifyJs:会去除注释和额外的空格,替换变量名,合并var表达式,也会进行一些其他方式的优化
- 使用中根据自己的项目需要选择
文件的精简与压缩
JS的异步加载:无阻塞脚本加载
异步加载(又称为非阻塞加载):等待页面加载完成之后再加载 JavaScript 文件。在浏览器下载执行js的同时,还会继续后续页面的处理
优点:js的延迟加载有助与提高页面的加载速度
异步加载原因有2:
(1)在JavaScript性能优化上,减少脚本文件大小并限制HTTP请求的次数仅仅是让界面响应迅速的第一步,现在的web应用功能丰富,js脚本越来越多,光靠精简源码大小和减少次数不总是可行的,即使是一次HTTP请求,但文件过于庞大,界面也会被锁死很长一段时间,这明显不好的,因此,无阻塞加载技术应运而生。简单来说,就是页面在加载完成后才加载js代码,也就是在window对象的load事件触发后才去下载脚本
(2)实现异步加载的原因:有些 js 代码并不是页面初始化的时候就立刻需要的,而稍后的某些情况才需要的。延迟加载就是一开始并不加载这些暂时不用的js,而是在需要的时候或再通过js 的控制来异步加载。
JS延迟(异步)加载
- 为
<script>
标签添加defer和async属性
(1) defer="defer":
HTML4以后为<script>
标签定义了一个扩展属性,该属性用来通知浏览器,要加载的这段脚本不会修改DOM,因此代码是可以安全的去延迟执行的。
例如 JavaScript代码中的document.write()
方法将不会起作用,浏览器遇到这样的代码将会忽略,并继续执行后面的代码。属性值只能是 defer,与属性名相同。在HTML语法格式下,也允许不定义属性值,仅仅使用属性名
带defer属性的<script>
标签在DOM完成加载之前都不会去执行,无论是内嵌还是外链方式。
(2) async="true/false"
该属性为html5中新增的属性,它的作用是能够异步地下载和执行脚本,不因为加载脚本而阻塞页面的加载。一旦下载完毕就会立刻执行。
defer与async相同点:都是采用的并行下载,下载过程中不会有阻塞
defer与async不同点:两者的执行时机不同。async加载完成后就会自动执行代码,但是defer需要等待页面加载完成后才会执行。
以下代码虽然将<script>
元素放在了<head>
元素中,但因为添加了defer属性,因此脚本在执行时不会影响页面的构造(即脚本会被延迟到整个页面都解析完毕之后再执行)
然后浏览器会并行下载jquery.js,script2.js,script3.js的script,而不会阻塞页面后续处理
注意:(1)所有的defer脚本保证是按顺序依次执行的
(2)defer属性只适用于外部脚本文件
<head>
//脚本1
<script defer src="js/vendor/jquery.js"></script>
//脚本2
<script defer src="js/script2.js"></script>
//脚本3
<script defer src="js/script3.js"></script>
</head>
- 动态添加脚本元素
优点:无论这段脚本是在何时启动下载,它的下载和执行过程都不会阻塞页面的其他进程,我们甚至可以直接添加到头部head标签中,都不会影响其他部分。
通过这种方式下载文件后,代码就会自动执行。但是在现代浏览器中,这段脚本会等待所有动态节点加载完成后再执行。这种情况下,为了确保当前代码中包含的别的代码的接口或者方法能够被成功调用,就必须在别的代码加载前完成这段代码的准备
解决的具体操作思路是:现代浏览器会在script标签内容下载完成后接收一个load事件,我们就可以在load事件后再去执行我们想要执行的代码加载和运行,在IE中,它会接收loaded和complete事件,理论上是loaded完成后才会有completed,但实践告诉我们他两似乎并没有个先后,甚至有时候只会拿到其中的一个事件,我们可以单独的封装一个专门的函数来体现这个功能的实践性
function LoadScript(url, callback) {
var script = document.createElement('script');
script.type = 'text/javascript';
// IE浏览器下
if (script.readyState) {
script.onreadystatechange = function () {
if (script.readyState == 'loaded' || script.readyState == 'complete') {
// 确保执行两次
script.onreadystatechange = null;
// todo 执行要执行的代码
callback()
}
}
} else {
script.onload = function () {
callback();
}
}
script.src = 'file.js';
document.getElementsByTagName('head')[0].appendChild(script);
}
- 使用setTimeout延迟方法的加载时间
<script type="text/javascript" >
function A(){
$.post("/lord/login",{name:username,pwd:password},function(){
alert("Hello");
});
}
$(function (){
setTimeout('A()', 1000); //延迟1秒
})
</script>
- XMLHttpRequest脚本注入,动态添加script标签
优点:(1)可以控制脚本是否要立即执行。因为我们知道新创建的script标签只要添加到文档界面中它就会立即执行,因此,在添加到文档界面之前,也就是在appendChild()之前,我们可以根据自己实际的业务逻辑去实现需求,到了想要让它执行的时候,再appendChild()即可(2)它的兼容性很好,所有主流浏览器都支持,它不需要像动态添加脚本的方式那样,我们自己去写特性检测代码。
缺点:由于是使用了XHR对象,获取这种资源有“域”的限制。资源必须在同一个域下才可以,不可以跨域操作。
var xhr = new XMLHttpRequest();
xhr.open('get','test.js',true);
xhr.onreadystatachange = function(){
if(xhr.readyState == 4){// 表示响应已经完成,可以获取并使用服务器的响应了
if(xhr.state >= 200 && xhr.state < 300 || xhr.state == 304){//200表示成功/OK
var script = document.createElement('script');
script.type = 'text/javascript';
script.text = xhr.requestText;
document.body.appendChild(script);
}
}
}
补充方法XHR.readyState的五种状态
XHR.readyState == 状态(0,1,2,3,4)
0:请求未初始化,还没有调用 open()
1:请求已经建立,但是还没有发送,还没有调用 send()
2:请求已发送,正在处理中(通常现在可以从响应中获取内容头)
3:请求在处理中;通常响应中已有部分数据可用了,没有全部完成
4:响应已完成;可以获取并使用服务器的响应了
补充方法XHR.status常见的几种状态
XHR.status == 200,300,404
100——客户必须继续发出请求
101——客户要求服务器根据请求转换HTTP协议版本
200——成功
201——提示知道新文件的URL
300——请求的资源可在多处得到
301——删除请求数据
404——没有发现文件、查询或URl
500——服务器产生内部错误 index.php
总结
JS延迟加载的方式有:
(1)为<script>
标签添加defer和async属性
(2)动态添加脚本元素
(3)使用setTimeout延迟方法的加载时间
(4)XMLHttpRequest脚本注入,动态添加script标签
参考博客:
博客园:延迟加载的方式
件数:javascript延迟加载
ajax方法
需要好好读的博客:前端性能优化:细说JavaScript的加载与执行