JavaScript 性能优化(内存管理、防抖节流)
性能优化需要理解内存管理机制,并合理使用防抖节流控制执行频率。
内存管理基础
内存生命周期
- 分配:声明变量、对象时分配内存
- 使用:读写内存中的值
- 释放:不再需要时释放内存
JavaScript
// 分配
let obj = { name: 'Alice' };
// 使用
console.log(obj.name);
// 释放(解除引用)
obj = null;
垃圾回收机制
JavaScript 使用标记-清除算法:
JavaScript
// 可达对象不会被回收
let user = { name: 'Alice' };
user = null; // { name: 'Alice' } 变为不可达,将被回收
// 循环引用(现代 GC 可处理)
function createCycle() {
let obj1 = {};
let obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
return 'done';
}
createCycle(); // obj1、obj2 离开作用域后可被回收
常见内存泄漏
1. 意外的全局变量
JavaScript
function leak() {
bar = 'global'; // 意外创建全局变量
}
// 解决:使用严格模式
'use strict';
function noLeak() {
const bar = 'local';
}
2. 未清理的定时器
JavaScript
// 泄漏
function startTimer() {
const data = new Array(1000000);
setInterval(() => {
console.log(data.length); // data 一直被引用
}, 1000);
}
// 解决
let timerId;
function startTimer() {
const data = new Array(1000000);
timerId = setInterval(() => {
console.log(data.length);
}, 1000);
}
function stopTimer() {
clearInterval(timerId);
}
3. 未移除的事件监听
JavaScript
// 泄漏
class Component {
constructor() {
this.data = new Array(1000000);
window.addEventListener('resize', this.handleResize);
}
handleResize = () => {
console.log(this.data.length);
}
}
// 解决
class Component {
constructor() {
this.data = new Array(1000000);
window.addEventListener('resize', this.handleResize);
}
handleResize = () => {
console.log(this.data.length);
}
destroy() {
window.removeEventListener('resize', this.handleResize);
}
}
4. 闭包引用
JavaScript
// 泄漏:闭包持有大对象
function createHandler() {
const largeData = new Array(1000000);
return () => console.log(largeData.length);
}
// 解决:只保留必要数据
function createHandler() {
const largeData = new Array(1000000);
const length = largeData.length;
return () => console.log(length);
}
防抖(Debounce)
延迟执行,在等待期间再次触发则重新计时:
JavaScript
function debounce(fn, delay) {
let timerId = null;
return function(...args) {
clearTimeout(timerId);
timerId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用
const handleInput = debounce((value) => {
console.log('搜索:', value);
}, 300);
input.addEventListener('input', (e) => {
handleInput(e.target.value);
});
立即执行版防抖
JavaScript
function debounceImmediate(fn, delay) {
let timerId = null;
return function(...args) {
if (!timerId) {
fn.apply(this, args); // 首次立即执行
}
clearTimeout(timerId);
timerId = setTimeout(() => {
timerId = null;
}, delay);
};
}
节流(Throttle)
固定间隔执行,忽略间隔内的重复触发:
JavaScript
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用
const handleScroll = throttle(() => {
console.log('滚动位置:', window.scrollY);
}, 200);
window.addEventListener('scroll', handleScroll);
定时器版节流
JavaScript
function throttleTimer(fn, interval) {
let timerId = null;
return function(...args) {
if (!timerId) {
timerId = setTimeout(() => {
fn.apply(this, args);
timerId = null;
}, interval);
}
};
}
防抖 vs 节流
| 特性 | 防抖 | 节流 |
|---|---|---|
| 执行时机 | 停止触发后执行 | 固定间隔执行 |
| 适用场景 | 搜索输入、窗口resize | 滚动、拖拽 |
| 触发频率 | 最后一次 | 固定间隔 |
应用场景
JavaScript
// 搜索框输入
searchInput.addEventListener('input', debounce(search, 300));
// 窗口大小调整
window.addEventListener('resize', debounce(updateLayout, 200));
// 页面滚动
window.addEventListener('scroll', throttle(updatePosition, 100));
// 鼠标移动
document.addEventListener('mousemove', throttle(logPosition, 50));
要点总结
- JavaScript 自动垃圾回收,但仍需注意内存泄漏
- 常见泄漏:全局变量、定时器、事件监听、闭包
- 防抖:延迟执行,频繁触发时重置计时
- 节流:固定间隔执行,稀释触发频率
- 根据场景选择:搜索用防抖,滚动拖拽用节流
📝 发现内容有误?点击此处直接编辑