从卡顿到丝滑:JavaScript性能优化实战秘籍
引言
在当今的 Web 开发领域,JavaScript 作为前端开发的核心语言,其性能表现对网页的加载速度、交互响应以及用户体验有着举足轻重的影响。随着 Web 应用的复杂度不断攀升,功能日益丰富,用户对于网页性能的期望也越来越高。从电商平台的快速商品加载,到社交网络的实时动态刷新,再到在线游戏的流畅交互,每一个出色的 Web 应用背后,都离不开对 JavaScript 性能的精心雕琢。因此,深入理解并掌握 JavaScript 性能优化技巧,已成为前端开发者必备的核心技能之一 ,这不仅能够提升应用的竞争力,还能为用户带来更加卓越的使用体验。接下来,就让我们一同深入探索 JavaScript 性能优化的实战技巧。
一、性能优化前奏:剖析问题根源
在深入探讨 JavaScript 性能优化实战技巧之前,我们有必要先了解性能问题产生的根源。JavaScript 性能问题的产生往往是多种因素交织的结果,而其中最常见的罪魁祸首包括复杂计算、频繁的 DOM 操作以及内存管理不善等 。
复杂计算
复杂计算是导致 JavaScript 性能瓶颈的常见原因之一。当我们在代码中执行大量的数学运算、递归算法或者复杂的逻辑判断时,会消耗大量的 CPU 资源和时间。例如,在处理大数据集的排序和搜索时,如果使用效率低下的算法,如冒泡排序,随着数据量的增加,计算时间会呈指数级增长。如下是一个使用冒泡排序算法对数组进行排序的示例代码:
function bubbleSort(arr) {
const len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
// 测试
const largeArray = Array.from({ length: 10000 }, (_, i) => Math.floor(Math.random() * 10000));
console.time('bubbleSort');
bubbleSort(largeArray);
console.timeEnd('bubbleSort');
在上述代码中,使用冒泡排序算法对包含 10000 个随机元素的数组进行排序。冒泡排序的时间复杂度为 O (n^2),对于较大的数据集,其执行时间会非常长。通过console.time和console.timeEnd可以测量该函数的执行时间,在性能要求较高的场景下,这种低效的算法显然是不可取的。
DOM 操作频繁
DOM 操作也是影响 JavaScript 性能的重要因素。DOM(文档对象模型)是 JavaScript 与 HTML 页面交互的桥梁,然而,每次对 DOM 进行访问、修改或添加元素时,浏览器都需要重新计算布局和样式,这一过程被称为重排(reflow)和重绘(repaint) ,会消耗大量的性能。比如,在一个循环中频繁地创建和插入 DOM 元素:
const container = document.getElementById('container');
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
container.appendChild(div);
}
在这段代码中,每一次循环都创建一个新的div元素并将其插入到container中,这会导致浏览器进行 1000 次重排和重绘,严重影响性能。
内存管理不善
内存管理不善也是导致性能问题的潜在因素。JavaScript 具有自动垃圾回收机制,它会自动回收不再使用的内存。然而,如果我们在代码中存在不合理的引用关系,导致对象无法被正确回收,就会造成内存泄漏。例如,在一个全局作用域中创建了大量的对象,但在它们不再使用时没有释放引用:
// 全局变量,存储大量对象
let largeObjectArray = [];
function createLargeObjects() {
for (let i = 0; i < 10000; i++) {
const obj = {
data: new Array(1000).fill(i)
};
largeObjectArray.push(obj);
}
}
// 调用函数创建大量对象
createLargeObjects();
// 后续代码中,largeObjectArray不再使用,但由于全局引用,对象无法被垃圾回收
在上述示例中,createLargeObjects函数创建了 10000 个包含大量数据的对象,并将它们存储在全局变量largeObjectArray中。即使在后续代码中不再使用这些对象,由于largeObjectArray对它们的引用,垃圾回收机制无法回收这些对象占用的内存,从而导致内存泄漏,随着时间的推移,可能会使应用程序的性能逐渐下降。
这些性能问题不仅仅是代码层面的瑕疵,它们对用户体验和业务有着深远的负面影响。在用户体验方面,缓慢的页面加载速度和卡顿的交互响应会让用户感到烦躁和不满,导致用户流失。根据调查,页面加载时间每增加一秒,用户跳出率可能会增加 7% ,这对于任何在线业务来说都是一个巨大的损失。从业务角度来看,性能问题可能会影响搜索引擎排名,降低转化率,进而影响企业的收入和声誉。因此,解决 JavaScript 性能问题迫在眉睫,它是提升用户满意度、增强业务竞争力的关键所在。
二、实战优化技巧大放送
(一)优化代码结构
1. 精简全局变量
在 JavaScript 中,全局变量就像是一把双刃剑。一方面,它提供了便捷的全局访问能力,让数据在不同的函数和模块之间得以共享;但另一方面,过多地使用全局变量会对性能产生负面影响,增加命名冲突的风险,并且使得代码的维护和调试变得困难重重。当我们访问全局变量时,JavaScript 引擎需要在全局作用域中进行查找,这一过程相较于访问局部变量来说,耗费的时间和资源更多。例如,在一个大型的 Web 应用中,如果频繁地访问全局变量,随着代码量的增加和功能的复杂化,查找全局变量的开销会逐渐累积,从而降低应用的整体性能。
为了避免这些问题,我们应尽量减少全局变量的使用,转而使用局部变量。局部变量的作用域仅限于其所在的函数或代码块,这使得 JavaScript 引擎能够更快地定位和访问它们。例如,在一个函数中,如果需要多次使用某个全局变量,可以将其赋值给一个局部变量,然后在函数内部使用该局部变量。这样,不仅可以提高访问速度,还能增强代码的可读性和可维护性。以下是一个具体的示例:
// 全局变量
let globalData = [1, 2, 3, 4, 5];
function processData() {
// 将全局变量赋值给局部变量
let localData = globalData;
for (let i = 0; i < localData.length; i++) {
// 处理数据
localData[i] = localData[i] * 2;
}
return localData;
}
在上述代码中,globalData是一个全局变量,在processData函数内部,我们将其赋值给局部变量localData,然后在循环中使用localData进行数据处理。这样,每次访问localData时,JavaScript 引擎可以直接在函数的局部作用域中找到它,而无需在全局作用域中进行查找,从而提高了性能。
2. 巧用事件委托
事件委托是 JavaScript 中一种强大的事件处理技巧,它基于事件冒泡的原理,能够有效地提升代码的性能和可维护性。事件冒泡是指当一个元素上的事件被触发时,该事件会从目标元素开始,逐级向上传播到父元素,直到到达文档的根节点。利用这一特性,我们可以将事件监听器绑定到父元素上,而不是为每个子元素都单独绑定事件监听器,这样,当子元素触发事件时,父元素可以捕获到该事件并进行统一处理。
以列表项点击事件为例,假设我们有一个包含大量列表项的无序列表,需要为每个列表项添加点击事件,以显示其对应的内容。如果采用传统的方式,为每个列表项都绑定一个点击事件监听器,当列表项数量较多时,会创建大量的事件监听器,占用大量的内存资源,并且会增加浏览器的负担。而使用事件委托,我们只需将点击事件监听器绑定到父元素ul上,通过判断事件的目标元素(event.target)是否为列表项(li),来确定点击的是哪个列表项,并进行相应的处理。以下是两种方式的代码对比:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件委托示例</title>
</head>
<body>
<ul id="myList">
<li>列表项1</li>
<li>列表项2</li>
<li>列表项3</li>
<!-- 更多列表项 -->
</ul>
<script>
// 传统方式:为每个列表项绑定点击事件
const listItems = document.querySelectorAll('#myList li');
listItems.forEach(item => {
item.addEventListener('click', () => {
console.log('点击了:', item.textContent);
});
});
// 事件委托方式:将点击事件绑定到父元素
const parent = document.getElementById('myList');
parent.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log('点击了:', event.target.textContent);
}
});
</script>
</body>
</html>
在上述代码中,传统方式为每个列表项都绑定了一个点击事件监听器,而事件委托方式只在父元素ul上绑定了一个点击事件监听器。通过事件委托,不仅减少了事件监听器的数量,节省了内存资源,还能方便地处理动态添加的列表项。当有新的列表项被动态添加到ul中时,无需再次为其绑定点击事件监听器,因为父元素ul已经绑定了事件监听器,新添加的列表项触发的点击事件同样会冒泡到父元素并被处理。为了更直观地感受事件委托前后的性能差异,我们可以使用performance.now()方法来测量两种方式在绑定大量列表项点击事件时的耗时。假设我们有 1000 个列表项,以下是测试代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件委托性能测试</title>
</head>
<body>
<ul id="testList">
<!-- 这里省略1000个列表项的生成 -->
</ul>
<script>
// 生成1000个列表项
const list = document.getElementById('testList');
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `列表项${i + 1}`;
list.appendChild(li);
}
// 传统方式性能测试
const startTraditional = performance.now();
const traditionalItems = document.querySelectorAll('#testList li');
traditionalItems.forEach(item => {
item.addEventListener('click', () => {
// 这里可以添加点击后的处理逻辑
});
});
const endTraditional = performance.now();
console.log('传统方式绑定1000个列表项点击事件耗时:', endTraditional - startTraditional, '毫秒');
// 事件委托方式性能测试
const startDelegation = performance.now();
const parent = document.getElementById('testList');
parent.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
// 这里可以添加点击后的处理逻辑
}
});
const endDelegation = performance.now();
console.log('事件委托方式绑定1000个列表项点击事件耗时:', endDelegation - startDelegation, '毫秒');
</script>
</body>
</html>
在实际测试中,你会发现事件委托方式的耗时明显低于传统方式,这充分展示了事件委托在性能优化方面的优势。
3. 打磨循环优化
循环是 JavaScript 中常用的控制结构,用于重复执行一段代码。然而,不当的循环使用可能会导致性能问题,特别是在处理大量数据时。因此,优化循环对于提升 JavaScript 代码的性能至关重要。在循环优化中,减少循环体内的计算和选择最优的循环方式是两个关键的优化手段。
减少循环体内的计算可以显著提高循环的执行效率。如果在循环体内存在一些不依赖于循环变量的计算,或者可以在循环前预先计算的结果,应将这些计算移出循环体。例如,在计算数组元素的总和时,如果数组长度在循环过程中不会改变,那么可以在循环前预先计算数组长度,避免在每次循环中重复计算。以下是优化前后的代码对比:
// 优化前
const arr = [1, 2, 3, 4, 5];
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
// 优化后
const arr = [1, 2, 3, 4, 5];
let sum = 0;
const len = arr.length;
for (let i = 0; i < len; i++) {
sum += arr[i];
}
在上述代码中,优化前每次循环都需要计算arr.length,而优化后将arr.length预先计算并赋值给len,在循环中直接使用len,避免了重复计算,从而提高了循环的执行效率。
选择最优的循环方式也是优化循环性能的重要方面。JavaScript 中常见的循环方式有for循环、while循环、do...while循环、for...of循环和for...in循环等,它们各有特点,适用于不同的场景。一般来说,for循环和while循环在性能上较为接近,适用于已知循环次数的场景;for...of循环主要用于遍历可迭代对象,如数组、字符串等,它提供了更简洁的语法,并且在遍历数组时性能与for循环相当;而for...in循环主要用于遍历对象的属性,由于它需要遍历对象的整个原型链,性能相对较低,不适合用于遍历数组。以下是使用不同循环方式遍历数组的性能测试代码:
const largeArray = Array.from({ length: 100000 }, (_, i) => i);
// for循环性能测试
const startFor = performance.now();
for (let i = 0; i < largeArray.length; i++) {
// 这里可以添加处理逻辑
}
const endFor = performance.now();
console.log('for循环遍历100000个元素耗时:', endFor - startFor, '毫秒');
// while循环性能测试
const startWhile = performance.now();
let j = 0;
while (j < largeArray.length) {
// 这里可以添加处理逻辑
j++;
}
const endWhile = performance.now();
console.log('while循环遍历100000个元素耗时:', endWhile - startWhile, '毫秒');
// for...of循环性能测试
const startForOf = performance.now();
for (const item of largeArray) {
// 这里可以添加处理逻辑
}
const endForOf = performance.now();
console.log('for...of循环遍历100000个元素耗时:', endForOf - startForOf, '毫秒');
// for...in循环性能测试
const startForIn = performance.now();
for (const index in largeArray) {
// 这里可以添加处理逻辑
}
const endForIn = performance.now();
console.log('for...in循环遍历100000个元素耗时:', endForIn - startForIn, '毫秒');
通过实际测试可以发现,for循环、while循环和for...of循环在遍历数组时性能较为接近,而for...in循环的耗时明显更长。因此,在遍历数组时,应优先选择for循环、while循环或for...of循环,避免使用for...in循环。
(二)内存管理之道
1. 杜绝内存泄漏
内存泄漏是指程序中已分配的内存由于某种原因无法被释放,导致内存资源的浪费,随着时间的推移,可能会使程序的性能逐渐下降,甚至导致程序崩溃。在 JavaScript 中,虽然有自动垃圾回收机制(Garbage Collection,GC)来管理内存,但如果代码编写不当,仍然可能出现内存泄漏的情况。其中,定时器和事件监听器是常见的导致内存泄漏的原因。
定时器(如setTimeout和setInterval)在使用时,如果没有正确清除,会导致其回调函数中引用的对象无法被垃圾回收。例如,在一个函数中创建了一个定时器,其回调函数引用了外部的一个大对象:
function createTimer() {
const largeObject = { data: new Array(100000).fill('test') };
const timer = setInterval(() => {
// 回调函数中引用了largeObject
console.log(largeObject.data.length);
}, 1000);
// 没有清除定时器
}
在上述代码中,由于定时器timer一直在运行,其回调函数中对largeObject的引用使得largeObject无法被垃圾回收,即使createTimer函数执行完毕,largeObject所占用的内存也不会被释放,从而导致内存泄漏。为了避免这种情况,当不再需要定时器时,应使用clearTimeout或clearInterval方法清除定时器:
function createTimer() {
const largeObject = { data: new Array(100000).fill('test') };
const timer = setInterval(() => {
console.log(largeObject.data.length);
}, 1000);
// 假设在某个条件下清除定时器
setTimeout(() => {
clearInterval(timer);
}, 5000);
}
在这个改进后的代码中,通过setTimeout在 5 秒后调用clearInterval方法清除了定时器timer,这样当定时器被清除后,其回调函数不再被执行,对largeObject的引用也随之消失,largeObject就可以被垃圾回收,避免了内存泄漏。
事件监听器同样需要正确管理,否则也会导致内存泄漏。当为一个 DOM 元素添加事件监听器后,如果在元素被移除或不再需要事件监听器时没有及时移除,事件监听器所引用的元素将无法被垃圾回收。例如,为一个按钮添加点击事件监听器:
const button = document.getElementById('myButton');
const clickHandler = () => {
console.log('按钮被点击');
};
button.addEventListener('click', clickHandler);
// 假设按钮被移除,但没有移除事件监听器
button.parentNode.removeChild(button);
在上述例子中,按钮被移除后,由于没有移除其点击事件监听器clickHandler,clickHandler仍然引用着按钮元素,导致按钮元素无法被垃圾回收,从而造成内存泄漏。为了避免这种情况,在移除元素或不再需要事件监听器时,应使用removeEventListener方法移除事件监听器:
const button = document.getElementById('myButton');
const clickHandler = () => {
console.log('按钮被点击');
};
button.addEventListener('click', clickHandler);
// 移除按钮前,先移除事件监听器
button.removeEventListener('click', clickHandler);
button.parentNode.removeChild(button);
在这个改进后的代码中,在移除按钮之前,先使用removeEventListener方法移除了按钮的点击事件监听器clickHandler,这样当按钮被移除时,其不再被任何事件监听器引用,就可以被垃圾回收,避免了内存泄漏。
闭包也是需要注意内存管理的一个方面。闭包是指函数内部返回一个函数,并且该内部函数可以访问外部函数的变量。如果闭包使用不当,也可能导致内存泄漏。例如,一个函数返回一个闭包,该闭包引用了外部函数的一个大对象:
function createClosure() {
const largeObject = { data: new Array(100000).fill('test') };
return () => {
// 闭包中引用了largeObject
console.log(largeObject.data.length);
};
}
const closureFunction = createClosure();
在上述代码中,createClosure函数返回的闭包closureFunction引用了largeObject,只要closureFunction存在,largeObject就无法被垃圾回收,从而导致内存泄漏。为了避免闭包引起的内存泄漏,应确保闭包外部不再引用闭包内部的变量,或者在适当的时候手动释放对闭包的引用。例如,当不再需要closureFunction时,可以将其设置为null:
function createClosure() {
const largeObject = { data: new Array(100000).fill('test') };
return () => {
console.log(largeObject.data.length);
};
}
const closureFunction = createClosure();
// 假设在某个时刻不再需要closureFunction
closureFunction = null;
在这个改进后的代码中,当将closureFunction设置为null后,对闭包的引用被释放,闭包中引用的largeObject也可以被垃圾回收,避免了内存泄漏。
2. 优化对象创建与销毁
在 JavaScript 中,对象的创建和销毁是常见的操作,但如果不合理地进行对象创建和销毁,可能会影响性能。对象池模式是一种优化对象创建和销毁的有效方法,它通过预先创建一组对象并重复使用这些对象,避免了频繁地创建和销毁对象所带来的性能开销。对象池模式的原理是在程序初始化时,创建一定数量的对象并存储在一个池中,当需要使用对象时,从池中获取对象,使用完毕后再将对象放回池中,而不是每次都创建新的对象。这种方式适用于那些创建成本较高的对象,如数据库连接对象、图形绘制对象等。
例如,在一个游戏开发中,需要频繁地创建和销毁子弹对象。如果每次发射子弹时都创建一个新的子弹对象,会消耗大量的性能。而使用对象池模式,可以在游戏初始化时创建一定数量的子弹对象并放入对象池中,当玩家发射子弹时,从对象池中获取一个子弹对象并使用,当子弹超出屏幕或击中目标时,将子弹对象放回对象池中,供下次使用。以下是一个简单的对象池模式示例代码:
class Bullet {
constructor() {
// 初始化子弹属性
this.x = 0;
this.y = 0;
this.speed = 10;
}
fire(x, y) {
this
## 三、性能分析工具助力
“工欲善其事,必先利其器”,在JavaScript性能优化的道路上,性能分析工具犹如我们的得力助手,能够帮助我们快速、准确地定位性能瓶颈,为优化工作提供有力的数据支持。下面,让我们一起来认识几款常用的性能分析工具。
### Chrome DevTools
Chrome DevTools是Chrome浏览器内置的一套强大的开发者工具,其中的Performance面板和Memory面板在JavaScript性能分析中发挥着至关重要的作用。
Performance面板可以记录网页的性能数据,包括JavaScript的执行时间、渲染过程、网络请求等。通过它,我们可以直观地看到页面加载过程中各个阶段的耗时,从而找出性能瓶颈所在。例如,在一个电商网站的页面加载过程中,我们使用Performance面板进行分析,发现某个JavaScript函数的执行时间过长,导致页面渲染延迟。通过进一步分析该函数的代码逻辑,我们发现其中存在一个复杂的循环计算,经过优化算法后,页面加载速度得到了显著提升。具体使用时,我们可以在Chrome浏览器中打开目标网页,按下F12键打开DevTools,切换到Performance面板,点击录制按钮,然后进行页面操作,如刷新页面、点击按钮等,操作完成后停止录制,即可查看详细的性能分析报告。报告中会以时间轴的形式展示各个事件的发生时间和持续时间,通过对这些数据的分析,我们可以针对性地进行性能优化。
Memory面板则主要用于分析内存的使用情况,帮助我们检测内存泄漏和优化内存占用。它可以实时监测页面的内存变化,查看对象的创建和销毁情况。比如,在一个单页应用中,随着用户的不断操作,内存占用持续上升,通过Memory面板的分析,我们发现是由于某些事件监听器没有及时移除,导致相关对象无法被垃圾回收,从而造成了内存泄漏。通过在适当的时机移除这些事件监听器,成功解决了内存泄漏问题,优化了应用的内存性能。在使用Memory面板时,我们可以通过拍摄堆快照(Heap Snapshot)来记录当前内存中的对象状态,对比不同时间点的堆快照,找出内存泄漏的根源。此外,还可以使用时间线(Timeline)功能,观察内存使用随时间的变化趋势,及时发现内存异常增长的情况。
### Lighthouse
Lighthouse是一款由Google开发的开源工具,它可以对网页进行全面的性能评估,并生成详细的报告。Lighthouse的评估指标涵盖了性能、可访问性、最佳实践、搜索引擎优化等多个方面,为我们提供了全方位的优化建议。在性能方面,它会分析页面的加载速度、首次内容绘制时间、资源加载情况等关键指标,并给出具体的得分和优化建议。例如,对于一个新闻资讯类网站,Lighthouse检测到页面中存在一些未优化的图片资源,导致加载时间过长,影响了整体性能得分。根据Lighthouse的建议,我们对图片进行了压缩和格式转换,重新测试后,页面的性能得分得到了显著提高,加载速度也明显加快。使用Lighthouse非常简单,我们可以在Chrome浏览器中打开目标网页,点击右上角的菜单按钮,选择“更多工具”>“Lighthouse”,即可启动Lighthouse对当前网页进行评估。评估完成后,会生成一份详细的报告,报告中不仅包含各项指标的得分和描述,还会针对每个问题给出具体的优化建议和示例代码,帮助我们快速理解和解决性能问题。
### Web Vitals
Web Vitals是Google提出的一组关键的Web性能指标,用于衡量用户体验的质量。这些指标包括最大内容绘制(Largest Contentful Paint,LCP)、首次输入延迟(First Input Delay,FID)、累积布局偏移(Cumulative Layout Shift,CLS)等 。通过监测这些指标,我们可以从用户的角度了解页面的性能表现,并针对性地进行优化。例如,LCP指标反映了页面主内容的渲染速度,对于一个在线教育平台,用户希望能够尽快看到课程内容,如果LCP时间过长,会导致用户等待时间增加,降低用户体验。通过优化服务器端渲染、减少资源加载时间等措施,我们可以有效缩短LCP时间,提升用户体验。在实际应用中,我们可以使用web-vitals库在JavaScript代码中直接获取这些指标的值,并将其发送到服务器进行分析。例如:
```javascript
import { getCLS, getFID, getLCP } from 'web-vitals';
// 获取CLS指标
getCLS(console.log);
// 获取FID指标
getFID(console.log);
// 获取LCP指标
getLCP(console.log);
通过对这些指标的持续监测和优化,我们可以不断提升网页的性能,为用户提供更加流畅、稳定的体验。
webpack-bundle-analyzer
webpack-bundle-analyzer 是一个基于 Webpack 的插件,它可以帮助我们分析打包后的 JavaScript 文件,找出体积较大的模块,从而优化代码的加载性能。在大型项目中,随着功能的不断增加,打包后的 JavaScript 文件可能会变得越来越大,影响页面的加载速度。通过 webpack-bundle-analyzer 生成的可视化报告,我们可以直观地看到各个模块在打包后的大小和依赖关系,从而有针对性地进行代码优化。比如,在一个企业级管理系统中,使用 webpack-bundle-analyzer 分析后发现,某个第三方库的体积过大,且其中包含了一些我们未使用的功能。通过配置 Webpack 的 tree shaking 功能,去除了该库中未使用的代码,成功减小了打包文件的体积,提高了页面的加载速度。使用 webpack-bundle-analyzer 时,首先需要安装该插件,然后在 Webpack 的配置文件中添加相应的插件配置。例如:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
// 其他配置项
plugins: [
new BundleAnalyzerPlugin()
]
};
配置完成后,重新运行 Webpack 打包命令,会生成一个可视化的报告文件(通常是一个 HTML 文件),在浏览器中打开该文件,即可查看详细的模块分析信息。报告中以树状图或环形图的形式展示了各个模块的大小和依赖关系,通过对这些信息的分析,我们可以采取相应的优化措施,如代码分割、按需加载等,以提高代码的加载性能。
四、总结与展望
JavaScript 性能优化是一个持续且综合性的过程,它贯穿于 Web 开发的整个生命周期。通过对代码结构的精心优化,如精简全局变量、巧用事件委托和打磨循环逻辑,可以从根源上提升代码的执行效率,减少不必要的性能开销。在内存管理方面,杜绝内存泄漏,优化对象的创建与销毁,确保程序能够高效地利用内存资源,避免因内存问题导致的性能下降。而性能分析工具的合理运用,如 Chrome DevTools、Lighthouse、Web Vitals 和 webpack-bundle-analyzer 等 ,则为我们提供了洞察性能瓶颈的有力手段,使我们能够有的放矢地进行优化工作。
随着 Web 技术的飞速发展,JavaScript 性能优化也将面临新的机遇和挑战。在未来,我们可以期待更智能的优化算法和工具的出现,它们将能够自动检测和修复性能问题,进一步降低开发者的优化成本。同时,随着硬件性能的不断提升和浏览器技术的持续演进,JavaScript 的执行效率也将得到进一步提高。但这并不意味着我们可以忽视性能优化,相反,随着 Web 应用的复杂度不断增加,对性能的要求也会越来越高,性能优化将始终是前端开发中不可或缺的重要环节。
对于广大前端开发者而言,持续学习和掌握最新的性能优化技巧和工具,是保持竞争力的关键。在实际项目中,我们应养成良好的编码习惯,将性能优化的理念融入到每一行代码中,从细节入手,不断提升 Web 应用的性能和用户体验。只有这样,我们才能在快速发展的 Web 开发领域中立于不败之地,为用户打造出更加流畅、高效的 Web 应用。
相关文章:
从卡顿到丝滑:JavaScript性能优化实战秘籍
引言 在当今的 Web 开发领域,JavaScript 作为前端开发的核心语言,其性能表现对网页的加载速度、交互响应以及用户体验有着举足轻重的影响。随着 Web 应用的复杂度不断攀升,功能日益丰富,用户对于网页性能的期望也越来越高。从电商…...
win11平台下的docker-desktop中的volume位置问题
因为需要搞个本地的mysql数据库,而且本地安装的程序较多,不想再安mysql了,就想到使用docker来安装。而且因为数据巨大,所以想到直接使用转移data文件夹的方式。 各种查询,而且还使用ai查询,他们都提到&…...
掌握Multi-Agent实践(七):基于AgentScope分布式模式实现多智能体高效协作[并行加速大模型辅助搜索、分布式多用户协同辩论赛]
之前的案例都是运行在单台机器上以单进程形式运行,受限于 Python 的全局解释器锁,实际只能有效利用一个 CPU 的计算资源,并且无法支持多个用户从自己的电脑上接入同一个 Multi-Agent 应用进行交互。为了提高运行效率并支持多用户接入同一个应用中,AgentScope 提供了分布式…...
2023CCPC河南省赛暨河南邀请赛个人补题ABEFGHK
Dashboard - 2023 CCPC Henan Provincial Collegiate Programming Contest - Codeforces 过题难度:A H F G B K E 铜奖: 2 339 银奖: 3 318 金奖: 5 523 A: 直接模拟 // Code Start Here int t;cin >> t;while(t-…...
基于基金净值百分位的交易策略
策略来源:睿思量化小程序 基金净值百分位,是衡量当前基金净值在过去一段时间内的相对位置。以近一年为例,若某基金净值百分位为30%,意味着过去一年中有30%的时间基金净值低于当前值,70%的时间高于当前值。这一指标犹如…...
Ubuntu20.04下如何源码编译Carla,使用UE4源码开跑,踩坑集合
一、简介 作为一个从事算法研究的人员,无人驾驶仿真一直是比较重要的一部分,但是现在比较常见的算法验证都是在carla这个开源仿真平台上做的,所以我有二次开发carla的需求,今天就来讲讲编译CARLA。 网上的教材很多,但还是推荐大家看官网教程:Linux build - CARLA Simul…...
Windows 环境下 Docker Desktop 安装 + 汉化
一、WSL2Ubuntu20.04以及Docker Desktop安装 在开发过程中,Docker 是一款极为实用的工具。本教程专为 Windows 用户打造,将全面且细致地讲解 Docker Desktop 的安装与配置步骤,助您迅速入门并熟练掌握。同时,考虑到语言使用习惯&…...
HDMI信号采集器连OBS没有声音的问题
Windows中的设置 windows中可以按客服发来的视频做参考。 视频教程: https://jvod.300hu.com/vod/product/79b234d5-81f0-4255-8680-693de7b4b0d4/715859bc4d83490bb977d8bc0061bd5e.mp4 Linux中的设置 在linux中发现视频不太一样,设置完没有声音。…...
Remote Desktop安卓远程无法使用中文输入法
新版remote desktop包括windows app都无法使用中文输入法 下载旧版才能用8.8.1.56.294 感谢评论区大哥提供的方案...
maven报错 You have to use a classifier to attach supplemental artifacts
完整报错 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-jar-plugin:3.3.0:jar (default-jar) on project **: You have to use a classifier to attach supplemental artifacts to the project instead of replacing them. -> [Help 1] 解决办法 修改…...
maven dependencyManagement标签作用
dependencyManagement作用 1. 确保多模块间依赖版本一致 在父 POM 中定义依赖的版本、作用域(Scope)、排除项(Exclusions)等,子模块引用这些依赖时无需指定版本 2.灵活覆盖 子模块可以按需覆盖父 POM 中定义的版…...
Flink 运维监控与指标采集实战(Prometheus + Grafana 全流程)
一、引言:为什么 Flink 运维监控如此重要? 在实时计算场景中,Flink 作业 724 小时运行,对性能、资源、故障感知、状态变化的实时监控非常关键。没有有效的运维可观测体系: 不知道任务是否在稳定运行 发生问题难以快速定位 无法感知背压、延迟、反压等状态 因此,构建完善…...
计算机视觉----基于锚点的车道线检测、从Line-CNN到CLRNet到CLRKDNet 本文所提算法Line-CNN 后续会更新以下全部算法
本文所提算法如下: 叙述按时间顺序 你也可以把本文当作快速阅读这几篇文献的一个途径 所有重要的部分我都已经标注并弄懂其原理 方便自己也是方便大家 Line-CNN:基于线提议单元的端到端交通线检测 摘要 交通线检测是一项基础且具有挑战性的任务。以往的…...
Spring MVC HttpMessageConverter 的作用是什么?
HttpMessageConverter (HTTP 消息转换器) 是 Spring MVC 框架中一个非常核心的组件,它的主要作用是在 HTTP 请求、响应体与 Java 对象之间进行双向转换。 核心作用: 读取请求体 (Request Body) 到 Java 对象: 当 Controller 方法的参数使用 …...
WebGL 3着色器和GLSL
我们之前提到过着色器和GLSL,但是没有涉及细节,你可能已经对此有所了解, 但以防万一,这里将详细讲解着色器和GLSL。 在工作原理中我们提到,WebGL每次绘制需要两个着色器, 一个顶点着色器和一个片段着色器&…...
Hugging Face 中 LeRobot 使用的入门指南
相关源文件 .github/ISSUE_TEMPLATE/bug-report.yml .github/PULL_REQUEST_TEMPLATE.md README.md examples/1_load_lerobot_dataset.py examples/2_evaluate_pretrained_policy.py examples/3_train_policy.py lerobot/scripts/eval.py lerobot/scripts/train.py 本页面提供 …...
从零开始学习three.js(18):一文详解three.js中的着色器Shader
在WebGL和Three.js的3D图形渲染中,着色器(Shader) 是实现复杂视觉效果的核心工具。通过编写自定义的着色器代码,开发者可以直接操作GPU,实现从基础颜色渲染到动态光照、粒子效果等高级图形技术。本文将深入解析Three.j…...
Trae - 国人Cursor的免费平替产品
前情提要:Cursor-零基础使用flutter完成贪吃蛇游戏-迄今为止世上最牛的AI编辑工具,不需要程序员也可以编程 Cursor 不是我的最佳选择 Cursor 是基于 VSCode 进化而来,虽然好用,但总结下来有几点点是我有所顾虑的。 第一&#x…...
【PmHub后端篇】PmHub中Seata分布式事务保障任务审批状态一致性
在分布式系统中,事务管理是保证数据一致性的关键。本文将深入探讨在PmHub系统中,如何利用Seata分布式事务来保证任务审批状态的一致性。分布式事务在面试中是常见的考点,网上教程多偏理论,而实际项目中的应用更为关键。 1 事务基…...
2025年长三角高校数模竞赛B题Q1-Q3详细求解与Q4详细分析
B题 空气源热泵供暖的温度预测 问题背景 空气源热泵是一种与中央空调类似的设备,其结构主要由压缩主机、热交换 器以及末端构成,依靠水泵对末端房屋提供热量来实现制热。空气源热泵作为热 惯性负载,调节潜力巨大。工作时通过水循环系统将…...
插槽(Slot)的使用方法
插槽是Vue.js中一个强大的功能,允许你在组件中预留位置,让父组件可以插入自定义内容。以下是插槽的主要使用方法: 基本插槽 <!-- 子组件 ChildComponent.vue --> <template><div><h2>子组件标题</h2><slo…...
Milvus Docker 部署教程
1. 环境准备 确保您的系统已安装: DockerDocker ComposePython (用于运行客户端代码) 2. 项目结构 创建项目目录并设置以下文件: milvus-docker/ ├── docker-compose.yml ├── main.py3. 配置文件 docker-compose.yml version: 3.5services:…...
Vue 3 中 watch 的使用与深入理解
在 Vue 3 的 Composition API 中,watch 是一个非常强大的工具,用于监听响应式数据的变化并做出相应的处理。本文将通过一段实际代码来深入解析 watch 的行为和使用技巧。 🧩 示例代码回顾 import { reactive, watch } from vueconst state …...
嵌入式学习笔记 D21:双向链表的基本操作
双向链表的定义与创建双向链表的插入双向链表的查找双向链表的修改双向链表的删除双向链表的逆序MakeFile工具使用 一、双向链表的定义与创建 1.双向链表的定义: 双向链表是在单链表的每个结点中,再设置一个指向其前一个结点的指针域。 struct DOUNode…...
pciutils-3.5.5-win64工具的使用方法
目录 简介: 方法一:使用设备管理器 方法二:pciutils-3.5.5-win64 简介: window系统下查看PCIe设备信息比较困难 linux版本下,查看PCIE的信息比较容易,可在安装插件后直接使用命令得出。 例如…...
全息美AISEO引领AIGEO新趋势
内容概要 在数字化营销变革的浪潮中,全息美AISEO为企业注入了全新的活力。通过结合先进的技术与创造性的策略,AISEO不仅提升了品牌的可见度,更通过精准的用户定位,实现了信息的高效传播。尤其在当下竞争日益激烈的市场环境中&…...
平滑过滤值策略
该策略是一种基于技术分析的交易策略,主要通过计算一系列指标来判断市场趋势,并根据这些指标生成交易信号。 策略概述 该策略的核心在于利用多个技术指标来分析市场动态,并据此制定交易决策。它结合了价格动量、波动性和趋势跟踪等多种因素,旨在提高交易的准确性和效率。…...
Windows平台OpenManus部署及WebUI远程访问实现
前言:继DeepSeek引发行业震动后,Monica.im团队最新推出的Manus AI 产品正席卷科技圈。这款具备自主思维能力的全能型AI代理,不仅能精准解析复杂指令并直接产出成果,更颠覆了传统人机交互模式。尽管目前仍处于封闭测试阶段…...
极验证Geetest 通过python代理 透传
看了几个破解Geetest 四代滑块的视频和文章,有点望而生畏,github上也有收集极验四代滑块所有图片后经过ai训练做的破解代码,但作者说成功率只有90%。 于是考虑通过python透传的方法,j就是对自动化脚本套个【网页界面】的壳&#…...
JDK 1.8 全解析:从核心特性到企业实战的深度实践
引言 JDK 1.8 作为 Java 生态发展史上的里程碑版本,自 2014 年发布以来,凭借 Lambda 表达式、Stream API、新日期时间 API 三大核心特性,彻底重塑了 Java 编程范式。本文结合 Oracle 官方文档、蚂蚁集团、京东零售等企业级实战案例ÿ…...
OptiStruct实例:汽车声控建模
本章通过一个Tim-BaDy模型,展示利用AemmiecaviyMet工具进行声腔网格划分的过程因为南腔是考康边界的声场,所以在开展腔建机之的N营涉设物建大致封闭的边界模器东有内商院建权为例,首先需要准备自车身所合之的、首先委部)结构的有限元型车内声…...
k8s(12) — 版本控制和滚动更新(金丝雀部署理念)
金丝雀部署简介: 1、基本概念 金丝雀部署是一种软件开发中的渐进式发布策略,其核心思想是通过将新版本应用逐步发布给一小部分用户(即 “金丝雀” 用户),在真实环境中验证功能稳定性和性能表现,再逐步扩大发…...
基于matlab实现AUTOSAR软件开发---答疑6
最近有少朋友在咨询我关于模型生成A2L文件,之后在把elf文件的地址提取进去,生成最终的A2L的操作,这个其实可以看下mathwork的帮助文档,地址如下: https://www.mathworks.com/help/ecoder/ug/asap2-cdf-calibration.htmlhttps://www.mathworks.com/help/ecoder/ug/asap2-c…...
铜墙铁壁 - 服务网格的安全之道 (Istio 实例)
铜墙铁壁 - 服务网格的安全之道 (Istio 实例) 在微服务架构中,服务间的通信是频繁且复杂的。传统的安全模型常常假设内部网络是可信的,这在现代分布式系统和云原生(尤其是零信任)环境中是远远不够的。我们需要解决几个核心安全问题: 通信加密 (Encryption):如何确保服务 …...
计量——检验与代理变量
1.非嵌套模型的检验 1Davidson-Mackinnon test 判断哪个模型好 log(y)β0β1x1β2x2β3x3u log(y)β0β1log(x1)β2log(x2)β3log(x3)u 1.对logÿ…...
【C++】解析C++面向对象三要素:封装、继承与多态实现机制
解析C面向对象三要素:封装、继承与多态实现机制 1. 面向对象设计基石2. 封装:数据守卫者2.1 访问控制实现2.2 封装优势 3. 继承:代码复用艺术3.1 继承的核心作用3.2 继承类型对比3.3 典型应用场景3.4 构造函数与析构函数处理3.4.1 构造顺序控…...
c语言 写一个五子棋
c语言 IsWin判赢 display 画棋盘 判断落子的坐标是否已有棋子 判断落子坐标范围是否超出范围 // 五子棋 #include <stdio.h> #include <stdlib.h>// 画棋盘 void display(char map[][10]) {system("clear");printf(" 0 1 2 3 4 5 6 7 8 9\n&…...
深度解析 IDEA 集成 Continue 插件:提升开发效率的全流程指南
一、插件核心功能与应用场景 Continue 是一款专为 JetBrains IDE 设计的 AI 编程助手插件,基于大语言模型实现以下核心功能: 智能代码生成:支持根据自然语言描述生成完整方法、单元测试或设计模式(如线程安全单例模式࿰…...
Node.js
本文来源 : 腾讯元宝 Node.js 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境,采用 事件驱动 和 非阻塞 I/O 模型,专为构建高性能、可扩展的网络应用而设计。以下是其核心特性和应用场景的总结: 1…...
idea经常卡顿解决办法
一:前言 (1)使用idea工具开发过久,出现卡顿,等待响应 二:原因 (1)给idea设置的运行内存过小,需要使用的内存超过设置的内存 (2)插件过多&…...
【python】字典:: a list of dictionaries
No, actions is not a dictionary. It’s a list of dictionaries. Each item in the list is a dictionary with three key-value pairs: “measure” (number), “resource” (string), and “reason” (string). Here’s the structure: actions is a list []Each element…...
高效电脑隐私信息清理实用工具
软件介绍 本文介绍的这款Privacy Eraser,它是一款电脑系统隐私清理工具。 功能介绍 这款工具能够清理电脑里的多种信息,比如最近文件、临时文件、注册表信息,还有浏览器插件以及日志文件等等。 引导提示 注重隐私保护的小伙伴一定要将这款…...
在ubuntu系统中将vue3的打包文件dist 部署nginx 并且配置ssl证书 以https方式访问
在ubuntu系统中将vue3的打包文件dist 部署nginx 并且配置ssl证书 以https方式访问 确保 Nginx 已安装准备 Vue 3 打包文件配置 Nginx编辑 Nginx 配置文件启用配置文件测试 Nginx 配置重新加载 Nginx配置 SSL 证书获取 SSL 证书验证证书自动续期验证部署注意事项 确保 Nginx 已安…...
MH22D3开发高级UI应用,适配arm2d驱动
在资源有限的嵌入系统上,要开发出具有现代风格(圆弧,表盘,滚动,滑动,透明,图层叠加等)的UI应用,需要极高的cpu算力和ram,flash资源的支持。 但是往往鱼和熊掌…...
MongoDB数据库深度解析:架构、特性与应用场景
在现代应用程序开发中,数据存储技术的选择至关重要。在众多的数据库管理系统中,MongoDB以其灵活性和强大的功能迅速崛起,成为NoSQL数据库中的佼佼者。本文将深入解析MongoDB的架构、核心特性、性能优化及其在实际应用中的最佳实践,…...
LeetCode 235. 二叉搜索树的最近公共祖先 LeetCode 701.二叉搜索树中的插入操作 LeetCode 450.删除二叉搜索树中的节点
LeetCode 235. 二叉搜索树的最近公共祖先 思路: 根据二叉搜索树的特性,对 “基于二叉树的最近公共祖先 ” 进行优化,在二叉树寻找最近公共祖先时,需要分别对根节点的两个子树进行遍历来判断两个节点是异侧还是同侧。但是在二叉搜…...
GPU异步执行漏洞攻防实战:从CUDA Stream竞争到安全编程规范
点击 “AladdinEdu,同学们用得起的【H卡】算力平台”,H卡级别算力,按量计费,灵活弹性,顶级配置,学生专属优惠。 引言 在高校实验室的GPU加速计算研究中,多卡并行编程已成为提升深度学习训练效…...
[c语言日寄]数据结构:栈
【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋:这是一个专注于C语言刷题的专栏,精选题目,搭配详细题解、拓展算法。从基础语法到复杂算法,题目涉及的知识点全面覆盖,助力你系统提升。无论你是初学者,还是…...
day21:零基础学嵌入式之数据结构
一、双向链表(doulinklist) 1. 2.创建 struct DouLinkList *CreateDouLinkList() {struct DouLinkList *dl malloc(sizeof(struct DouLinkList));if(NULL dl){fprintf(stderr, "CreateDouLinkLis malloc");return NULL;}dl->head NUL…...
数据结构之图的应用场景及其代码
一,最小生成树 最小生成树(Minimum Spanning Tree, MST)是图论中的经典问题,旨在通过选择无向连通图中的边,使得所有节点连通且总边权最小。 1.1 普里姆(Prim)算法 普里姆算法是一种用于求解…...