本文基于《High Performance JavaScript》核心观点,系统梳理了提升前端性能的实用技巧。内容涵盖脚本加载位置与合并策略、变量作用域对访问速度的影响、DOM 操作的批量处理与缓存机制,以及减少页面重排重绘的具体代码方案。通过对比修改前后的代码示例,帮助开发者在实际项目中有效降低性能开销,提升用户体验。
JavaScript 性能优化概述
近期深入阅读了《High Performance JavaScript》一书,随后便投入到紧张的项目开发中。庆幸的是,最忙碌的一周已经过去,虽然本月因工作繁忙导致笔记产出有限,但在完成主要任务后的夜晚,仍坚持完成了阅读,最终顺利结束这本书的学习。
既然完成了阅读,便需要总结收获。整体而言,该书内容质量不错,但部分观点可能略显陈旧(受限于个人前端经验,或许未能完全领会其深层含义)。在阅读过程中,我编写了大量测试代码,并对书中推荐写法与传统大众写法进行了断点跟踪对比,从而更直观地理解实际性能差异。当然,仅靠断点跟踪无法覆盖所有情况。此处仅整理个人较为推崇的部分知识点,仅供参考。
脚本加载与合并策略
关于 JavaScript 文件的加载位置问题。
在 HTML 文件中,<script> 标签可以放置在 <head> 或 <body> 区域。由于 JavaScript 执行与 UI 渲染共享单线程,如果脚本加载阻塞了后续页面解析,浏览器会等待脚本完全加载并执行完毕后才继续渲染页面。这可能导致页面出现空白或卡顿现象。作为前端开发者,除了实现功能,更应关注优质的用户体验。为解决用户等待时的枯燥感,本文提出两种解决方案:
1. 若 JavaScript 文件无特殊要求必须在页面渲染前加载及编译完成,建议将脚本标签放置在 </body> 标签之前(即所有页面内容之后),而 CSS 文件仍保留在 <head> 区域,以避免用户看到布局混乱的页面。这种做法能确保用户先看到有布局的页面而非空白页。针对数据需通过 JS 请求加载的情况,可对数据加载顺序进行排序:优先执行急需呈现的接口,非关键数据可延后执行,并配合简单的加载动画或提示。
2. 若某些 JavaScript 文件必须优先执行才能正确展示页面内容,可在首个 JS 文件或页面初始阶段添加加载动画。使用有趣或可爱的动画场景能有效缓解用户等待时的无聊感,甚至可能提升用户对项目的兴趣,从而改善整体用户体验。
最终建议:尽可能将 <script> 标签放置在 </body> 标签之前加载,以优化用户体验。
关于 JavaScript 文件的合并策略。
在团队协作开发中,通常将不同功能的代码块分散在不同的 JS 文件中,以便多人协作、方便查找对应文件夹或文件,而非在单一长文件中寻找方法。这确实能提高团队开发效率,并利于新人进行二次开发与维护。然而,从页面性能角度审视,这正是问题所在。书中指出:每次 HTTP 请求都会带来额外的性能开销,因此下载一个 100 KB 的文件比下载四个 25 KB 的文件更快。
下载1个100KB的文件比下载4个25KB的文件要快,而开发过程中区分开各个文件又有很大的好处,那么合并这个问题也就放在开发完后再处理咯,相信这个操作大家都不会陌生吧,现在的前端工具这么丰富,各位习惯用什么压缩就用什么压缩吧~
此外,在文件加载方面还可利用 defer 和 async 属性,分别用于延迟加载和异步加载。现代浏览器大多已支持 defer 属性,尽管尚未完全普及,但值得尝试。有兴趣的开发者可自行查阅相关资料,此处仅作简要提及。
当前主流框架大多已配合懒加载和按需加载机制。
提升数据访问速度
关于更快速的数据访问。
对于浏览器而言,标识符所处的作用域层级越深,读写该标识符的速度越慢(原型链查找亦遵循此规律)。这一原理易于理解,可简单比喻为:杂货店离你家越远,买酱油所花的时间就越长……
若在当前函数内需多次使用某个变量值,可将其存储为局部变量以加速访问。具体案例如下:
//修改前
function showLi(){
var i = 0;
for(;i<document.getElementsByTagName("li").length;i++){ //一次访问document
console.log(i,document.getElementsByTagName("li")[i]); //三次访问document
};
};
//修改后
function showLi(){
var li_s = document.getElementsByTagName("li"); //一次访问document
var i = 0;
for(;i<li_s.length;i++){
console.log(i,li_s[i]); //三次访问局部变量li_s
};
};
DOM 操作与重排重绘优化
关于 DOM 操作的优化。
众所周知,DOM 操作比 JavaScript 代码执行更消耗性能。虽然无法完全避免 DOM 操作,但我们可以尽量减少其对性能的消耗。
以下通过代码示例解释该问题:
function innerLi_s(){
var i = 0;
for(;i<20;i++){
document.getElementById("Num").innerHTML="A"; //进行了20次循环,每次又有2次DOM元素访问:一次读取innerHTML的值,一次写入值
};
};
针对上述方法进行的改写方案如下:
function innerLi_s(){
var content ="";
var i = 0;
for(;i<20;i++){
content += "A"; //这里只对js的变量循环了20次
};
document.getElementById("Num").innerHTML += content; //这里值进行了一次DOM操作,又分2次DOM访问:一次读取innerHTML的值,一次写入值
};
减少 DOM 的重排与重绘。
元素布局改变、内容增删改或浏览器窗口尺寸变化均会导致重排(Reflow),而字体颜色或背景色修改则导致重绘(Repaint)。
对于类似以下代码的操作,现代浏览器大多已进行优化(将其合并为 1 次重排):
//修改前
var el = document.getElementById("div");
el.style.borderLeft = "1px"; //1次重排版
el.style.borderRight = "2px"; //又1次重排版
el.style.padding = "5px"; //还有1次重排版
//修改后
var el = document.getElementById("div");
el.style.cssText = "border-left:1px;border-right:2px;padding:5px"; //1次重排版
针对多重操作,以下三种方法也可有效减少重排和重绘的次数:
1. 先隐藏 DOM,操作后再显示(涉及 2 次重排,利用临时的 display:none)。
2. 使用 document.createDocumentFragment() 创建文档片段进行处理,操作完成后追加到页面(仅涉及 1 次重排)。
3. 通过 var newDOM = oldDOM.cloneNode(true) 创建 DOM 副本,修改副本后使用 oldDOM.parentNode.replaceChild(newDOM, oldDOM) 覆盖原 DOM(涉及 2 次重排)。
循环执行优化技巧
关于循环的优化。
这应是较为常见的写法,此处简要带过(后续仍将以代码加注释形式说明):
//修改前
var i = 0;
for(;i<arr.lengthli++){ //每次循环都需要获取数组arr的length
console.log(arr[i]);
}
//修改后
var i = 0;
var len = arr.length; //获取一次数组arr的length
for(;i<len;i++){
console.log(arr[i]);
}
//or
var i = arr.length;;
for(;i;i--){
console.log(arr[i]);
}
.odd{color:red}
.even{color:yellow}
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
var i = 0;
var lis = document.getElementsByTagName("li");
var len = lis.length;
for(;i<len;i++){
if(i&1){
lis[i].className = "even";
} else{
lis[i].className = "odd";
}
};
以上关于 JavaScript 提高性能的知识点总结即为全部内容,希望能对大家有所帮助,也希望大家多多支持本站。