减少内存占用,为什么提高性能呢?
当浏览器使用过多的内存时,它会变得缓慢、卡顿,并且更容易崩溃。这会影响你的浏览体验和效率。因此,如果你能减少浏览器的内存占用,你的页面就会更加流畅和快速地运行。为了减少浏览器的内存占用,就不得不提到JavaScript垃圾回收机制。
JavaScript的垃圾回收机制是自动的,并且是由JavaScript引擎负责调度和执行的。在通常情况下,为了最大限度地利用CPU和内存资源,垃圾回收机制会根据一定的算法和策略,自动寻找应该回收的内存对象,并对其进行回收。
虽然我们不能手动控制垃圾回收机制的执行,但我们可以通过一些编码技巧,减少内存分配和使用,从而促使垃圾回收机制的执行,减少垃圾回收的性能开销,提高代码执行速度。
一、减少全局变量
全局变量不易被垃圾回收,全局变量通常被定义在顶层作用域,它们会一直存在于整个应用程序的生命周期中,即使函数执行完毕之后,全局变量仍然存在于内存中,尽量减少全局变量的使用,并使用局部变量和函数封装。
// 不推荐的写法,使用全局变量
const globalVar = {};
function foo() {
globalVar.value = "hello";
}
// 推荐的写法,使用局部变量
function bar() {
let localVar = {};
localVar.value = "world";
}
二、及时释放不再使用的对象、变量和事件监听器
当你不再需要一个对象时,及时解除对它的引用,从而让垃圾回收器回收其内存。
// 不及时释放对象
let data = [1, 2, 3];
function processData() {
// do something with data
}
setInterval(function() {
processData();
}, 1000);
// 推荐写法
let obj = {};
// 执行操作
obj = null; // 释放内存占用
let btn = document.getElementById("btn");
btn.addEventListener("click", function() {
// 执行操作
btn.removeEventListener("click", arguments.callee); // 及时解绑事件监听器
}););
let obj = {a: 1, b: 2};
// 执行操作
delete obj.a; // 删除不再需要的属性
三、减少使用递归或无限循环等会占用大量内存的函数
循环引用可能导致内存泄漏。虽然现代浏览器的垃圾回收机制可以处理循环引用,但最好避免产生循环引用,在编写代码时使用开发者工具定期检查内存是否泄漏,并修复相关问题。
// 不推荐的递归写法,容易出现栈溢出
function recursion(n) {
if (n <= 1) {
return n;
}
return recursion(n - 1) + recursion(n - 2);
}
// 推荐的非递归写法
function fibonacci(n) {
if (n <= 1) {
return n;
}
let prev = 0, curr = 1;
for (let i = 2; i <= n; i++) {
let temp = curr;
curr = prev + curr;
prev = temp;
}
return curr;
}
四、使用对象池等技术,避免过多的内存分配和回收。
对于频繁创建和销毁的对象,可以使用对象池来减少垃圾回收的开销。对象池是一种常用的内存优化技术,它可以减少对象创建和销毁的次数,从而提高应用程序的性能。对象池可以用于缓存需要经常创建和销毁的对象,例如DOM元素和XHR对象等。
// 创建一个XHR对象池
const xhrPool = [];
function getXhr() {
if (xhrPool.length > 0) {
return xhrPool.pop();
} else {
return new XMLHttpRequest();
}
}
function releaseXhr(xhr) {
xhrPool.push(xhr);
}
// 使用XHR对象池
const xhr = getXhr();
xhr.open("GET", "https://example.com");
xhr.onload = function() {
// do something
releaseXhr(xhr);
};
xhr.send();
在这个例子中,我们在全局作用域内定义了一个XHR对象池,其中存放已经被创建但暂时不再使用的XHR对象。我们创建了getXhr
和releaseXhr
两个函数,用于从XHR池中获取对象和将对象还回池中,当需要使用XHR对象时,我们先通过getXhr
函数获取一个对象,执行完相应的操作后,再将对象还回对象池中,而不是直接将对象销毁。
这个技巧可以避免过多的内存分配和回收,从而提高性能。值得注意的是,对象池不是适用于所有情况的解决方案,使用对象池需要根据具体场景进行权衡和调整。
五、减少DOM的操作和避免频繁的重排和重绘,使用事件委托等技术避免大量的事件监听器堆积
<!-- 不推荐的写法,生成大量的事件监听器 -->
<ul>
<li onclick="handleClick(0)">Item 1</li>
<li onclick="handleClick(1)">Item 2</li>
<li onclick="handleClick(2)">Item 3</li>
<!-- ... -->
</ul>
<!-- 推荐的写法,使用事件委托 -->
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<!-- ... -->
</ul>
<script>
let list = document.getElementById("list");
function handleClick(e) {
let index = Array.from(list.children).indexOf(e.target);
// do something
}
list.addEventListener("click", handleClick);
</script>
六、合理使用计时器,减少计时器的执行频率,避免占用过多的CPU资源
在代码中合理使用 requestAnimationFrame
和 setTimeout/setInterval
等异步操作,以让垃圾回收器在空闲时间内执行。
// 不推荐的写法,间隔时间过短,会频繁占用CPU
setInterval(function() {
// do something
}, 100);
// 推荐的写法,间隔时间适当,避免占用过多的CPU
let timer = setInterval(function() {
// do something
}, 1000);
// 当不再需要执行计时器时,及时清除计时器
clearInterval(timer);
// 使用window.requestAnimationFrame()代替setInterval()或setTimeout(),
// 尽可能让浏览器在下一帧渲染前执行代码。
function animate() {
// do something
window.requestAnimationFrame(animate);
}
window.requestAnimationFrame(animate);
// 当不再需要执行动画时,及时停止requestAnimationFrame
window.cancelAnimationFrame(requestID);
七、减少使用闭包和匿名函数,减少内存占用和函数调用的开销
// 不推荐的写法,每次调用都会创建一个闭包对象
function foo() {
let counter = 0;
return function() {
return ++counter;
};
}
let bar = foo();
// 推荐的写法,将变量声明在函数外部
let counter = 0;
function add() {
return ++counter;
}
let baz = add();
八、优化数据结构和算法
使用更高效的数据结构和算法可以降低内存使用,减少垃圾回收的频率。例如在处理大量数据和复杂任务时,优化数据结构和算法可以显著提高代码的性能和效率,以免导致内存占用过高。
常见的优化方法包括:
- 数组去重:采用ES6新特性Set进行去重,或者使用Hash表加快检索。
- 链表操作:针对链表的常用操作,如翻转、合并、删除等,应该采用高效的算法实现,以避免频繁的遍历和操作。
- 缓存计算结果:针对一些耗时的计算操作,可以将计算结果缓存下来,以便下次重复使用。
- 分治算法:针对一些复杂的计算任务,可以采用分治算法或者动态规划等高效算法策略,避免枚举和暴力搜索带来的性能瓶颈。
- 位运算优化:针对某些二进制位运算操作,采用位运算符号代替乘、除、取模等算数运算,可以提高计算性能。
- 搜索算法优化:针对搜索算法,采用剪枝、双向搜索等优化方法,可以大幅减少搜索空间,提高搜索效率。
以上几点只是JavaScript优化数据结构和算法的几个方面,JavaScript的算法空间很大,优化的思路也不只局限于以上的方法,随着web开发的发展,算法和框架也在不断发展,我们应该根据应用场景和实际需要不断适应。
下面给出一些JavaScript优化数据结构和算法的示例:
1.数组去重
let arr = [1, 2, 3, 1, 2, 4];
// 使用Set对象进行快速去重
let uniqueArr = Array.from(new Set(arr));
console.log(uniqueArr); // [1, 2, 3, 4]
// 或者使用Hash表进行去重
let hash = {};
let result = [];
for (let i = 0; i < arr.length; i++) {
if (!hash[arr[i]]) {
hash[arr[i]] = true;
result.push(arr[i]);
}
}
console.log(result); // [1, 2, 3, 4]
2.链表操作
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
// 翻转链表
function reverseList(head) {
let prev = null, curr = head;
while (curr) {
let temp = curr.next;
curr.next = prev;
prev = curr;
curr = temp;
}
return prev;
}
// 合并两个有序链表
function mergeList(l1, l2) {
let dummyHead = new Node(null);
let curr = dummyHead;
while (l1 && l2) {
if (l1.val < l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.next;
}
curr = curr.next;
}
curr.next = l1 ? l1 : l2;
return dummyHead.next;
}
3.缓存计算结果
let cache = {};
function factorial(n) {
if (n === 1 || n === 0) {
return 1;
}
if (cache[n]) {
return cache[n];
}
let result = n * factorial(n - 1);
cache[n] = result;
return result;
}
4.分治算法
// 归并排序
function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
let mid = arr.length >> 1;
let left = arr.slice(0, mid);
let right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
while (i < left.length) {
result.push(left[i++]);
}
while (j < right.length) {
result.push(right[j++]);
}
return result;
}
5.位运算优化
// 将数字除以2
function divideByTwo(n) {
return n >> 1; // 相当于n / 2
}
// 判断奇偶性
function isEven(n) {
return (n & 1) === 0; // 最右边一位为0即为偶数
}
6.搜索算法优化
// 遍历8皇后问题
function solveQueens(n) {
let queens = new Array(n).fill(-1);
let res = [];
function backtrack(i) {
if (i === n) {
res.push([...queens]);
} else {
for (let j = 0; j < n; j++) {
if (isValid(i, j)) {
queens[i] = j;
backtrack(i + 1);
queens[i] = -1;
}
}
}
}
function isValid(row, col) {
for (let i = 0; i < row; i++) {
if (queens[i] === col ||
row - i === Math.abs(col - queens[i])) {
return false;
}
}
return true;
}
backtrack(0);
return res;
}
以上这些示例只是JavaScript优化数据结构和算法的冰山一角,它们展示出了不同场景下的优化思路,相信可以帮助你进行自己的优化实践。
Tips: 在开发的时候我们可以使用performance.now()
辅助性能检测,找出性能瓶颈,优化代码。
let startTime = performance.now();
// do something
let endTime = performance.now();
console.log("执行时间:", endTime - startTime);
虽然我们不能像C++那样控制垃圾回收,我们可以使用以上策略减少内存占用、减少垃圾回收的性能开销,提高代码执行速度。
请注意!!! 不同的JavaScript引擎可能具有不同的垃圾回收策略,因此实际效果可能稍有差别
原文链接:https://juejin.cn/post/7237694780371746876 作者:CodeIder