大家好,我是皮皮。
JavaScript 是单线程运行的,所以在在执行效率上并不是很高,随着用户体验的日益重视,前端性能对用户体验的影响备受关注,但由于性能问题相对复杂,接下来我们来了解下JavaScript如何提高性能;
从加载上优化:合理放置脚本位置
由于 JavaScript 的阻塞特性,在每一个
并且
的代码,加载体验都是不一样的。示例如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>js 引用的位置性能优化</title>
<script type="text/javascript" src="index-1.js"></script>
<script type="text/javascript" src="index-2.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
以上代码是一个简单的 html 界面,其中加载了两个 js 脚本文件和一个 css 样式文件,由于 js 的阻塞问题,当加载到 index-1.js 的时候,
其后面的内容将会被挂起等待,直到index-1.js 加载、执行完毕,才会执行第二个脚本文件 index-2.js,这个时候页面又将被挂起等待脚
本的加载和执行完成,一次类推,这样用户打开该界面的时候,界面内容会明显被延迟,我们就会看到一个空白的页面闪过,这种体验是
明显不好的,因此 我们应该尽量的让内容和样式先展示出来,将 js 文件放在 最后,以此来优化用户体验。如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>js 引用的位置性能优化</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="index-1.js"></script>
<script type="text/javascript" src="index-2.js"></script>
</body>
</html>
这段代码展示了在 HTML 文档中放置
成并显示给了用户,因此页面下载不会显得太慢。这是雅虎特别性能小组提出的优化 JavaScript 的首要规则:将脚本放在底部。
从请求次数上优化:减少请求次数
由于每个
这个问题在处理外链 JavaScript 文件时略有不同。考虑到 HTTP 请求会带来额外的性能开销,因此下载单个 100Kb 的文件将比下载 5 个 20Kb 的文件更快。也就是说,减少页面中外链脚本的数量将会改善性能。
通常一个大型网站或应用需要依赖数个 JavaScript 文件。您可以把多个文件合并成一个,这样只需要引用一个
需要特别提醒的是,把一段内嵌脚本放在引用外链样式表的之后会导致页面阻塞去等待样式表的下载。这样做是为了确保内嵌脚本在执行时能获得最精确的样式信息。因此,建议不要把内嵌脚本紧跟在标签后面。
有一点我们需要知道:页面加载的过程中,最耗时间的不是 js 本身的加载和执行,相比之下,每一次去后端获取资源,客户端与后台建立链接才是最耗时的,也就是大名鼎鼎的Http 三次握手,当然,http 请求不是我们这一次讨论的主题,因此,减少 HTTP 请求,是我们着重优化的一项,事实上,在页面中 js 脚本文件加载很很多情况下,它的优化效果是很显著的。
从加载方式上优化:无阻塞脚本加载
在 JavaScript 性能优化上,减少脚本文件大小并限制 HTTP 请求的次数仅仅是让界面响应 迅速的第一步,现在的 web 应用功能丰富,js 脚本越来越多,光靠精简源码大小和减少 次数不总是可行的,即使是一次 HTTP 请求,但文件过于庞大,界面也会被锁死很长一段 时间,这明显不好的,因此,无阻塞加载技术应运而生。简单来说, 就是 页面在加载完成后才加载 s js 代码,也就是在 w window 对象的 d load 事件触 发后才去下载脚本。要实现这种方式,常用以下几种方式:
延迟脚本加载( defer )
HTML4 为
带有 defer 属性的
延迟脚本加载( async )
HTML5 规范中也引入了 async 属性,用于异步加载脚本,其大致作用和 defer 是一样的,都是采用的并行下载,下载过程中不会有阻塞,但 不同点在于他们的执行时机,c async 需要加载完成后就会自动执行代码 ,但是 r defer 需要等待页面加载完成后才会执行。
从加载方式上优化:动态添加脚本元素
把代码以动态的方式添加的好处是:无论这段脚本是在何时启动下载,它的下载和执行过程都不会阻塞页面的其他进程,我们甚至可以直接添加带头部 head 标签中,都不会影响其他部分。因此,作为开发的你肯定见到过诸如此类的代码块:
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'file.js';
document.getElementsByTagName('head')[0].appendChild(script);
这种方式便是动态创建脚本的方式,也就是我们现在所说的动态脚本创建。通过这种方式下载文件后,代码就会自动执行。但是在现代浏览器中,这段脚本会等待所有动态节点加载完成后再执行。这种情况下,为了确保当前代码中包含的别的代码的接口或者方法能够被成功调用,就必须在别的代码加载前完成这段代码的准备。解决的具体操作思路是:现代浏览器会在 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);
}
LoadScript 函数接收两个参数,分别是要加载的脚本路径和加载成功后需要执行的回调函数,LoadScript 函数本身具有特征检测功能,根据检测结果(IE 和其他浏览器),来决定脚本处理过程中监听哪一个事件。实际上这里的 LoadScript()函数,就是我们所说的 LazyLoad.js(懒加载)的原型。
从加载方式上优化:XMLHttpRequest 脚本注入
通过 XMLHttpRequest 对象来获取脚本并注入到页面也是实现无阻塞加载的另一种方式,这个我觉得不难理解,这其实和动态添加脚本的方式是一样的思想,来看具体代码:
var xhr = new XMLHttpRequest();
xhr.open('get', 'file-1.js', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
// 如果从后台或者缓存中拿到数据,则添加到 script 中并加载执行。
var script = document.createElement('script');
script.type = 'text/javascript';
script.text = xhr.responseText;
// 将创建的 script 添加到文档页面
document.body.appendChild(script);
}
}
}
通过这种方式拿到的数据有两个优点:其一,我们可以控制脚本是否要立即执行,因为我们知道新创建的 script 标签只要添加到文档界面中它就会立即执行,因此,在添加到文档界面之前,也就是在 appendChild()之前,我们可以根据自己实际的业务逻辑去实现需求,到想要让它执行的时候,再 appendChild()即可。其二:它的兼容性很好,所有主流浏览器都支持,它不需要想动态添加脚本的方式那样,我们自己去写特性检测代码;但由于是使用了 XHR 对象,所以不足之处是获取这种资源有“域”的限制。资源 必须在同一个域下才可以,不可以跨域操作。
总结
减少 JavaScript 对性能的影响有以下几种方法:
将所有的
小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。