面试题答案
一键面试垃圾回收机制工作原理
JavaScript 采用标记清除(Mark and Sweep)算法来实现垃圾回收。
- 标记阶段:垃圾回收器从根对象(全局对象
window
在浏览器环境中,global
在 Node.js 环境中)出发,遍历所有可访问的对象,并为它们打上标记,表示这些对象是活动的(正在使用)。 - 清除阶段:垃圾回收器再次遍历堆内存,回收所有没有被标记的对象,释放它们所占用的内存空间。
可能导致内存泄漏的场景及避免方法
- 意外的全局变量
- 场景:在函数内部不使用
var
、let
或const
声明变量,该变量会自动成为全局变量。例如:
- 场景:在函数内部不使用
function foo() {
bar = "I am a global variable"; // 未声明,成为全局变量
}
- **避免方法**:始终使用`var`、`let`或`const`声明变量,严格模式(`'use strict';`)也会禁止这种意外创建全局变量的行为。
2. 事件监听未移除 - 场景:给 DOM 元素添加事件监听器后,如果该 DOM 元素从 DOM 树中移除,但事件监听器未被移除,就会导致内存泄漏。例如:
<div id="myDiv"></div>
<script>
const myDiv = document.getElementById('myDiv');
myDiv.addEventListener('click', function () {
console.log('Clicked');
});
// 假设这里移除了myDiv
document.body.removeChild(myDiv);
// 但事件监听器仍然存在,导致内存泄漏
</script>
- **避免方法**:在移除 DOM 元素之前,先移除事件监听器。可以给事件监听器函数赋值给一个变量,然后在移除 DOM 元素前使用`removeEventListener`移除监听器。例如:
<div id="myDiv"></div>
<script>
const myDiv = document.getElementById('myDiv');
const clickHandler = function () {
console.log('Clicked');
};
myDiv.addEventListener('click', clickHandler);
// 移除DOM元素前
myDiv.removeEventListener('click', clickHandler);
document.body.removeChild(myDiv);
</script>
- 闭包导致的内存泄漏
- 场景:如果闭包中引用了较大的对象,而闭包又长时间存在,会导致该对象无法被垃圾回收。例如:
function outer() {
const largeObject = { /* 包含大量数据的对象 */ };
return function inner() {
// inner函数形成闭包,引用了outer函数作用域中的largeObject
return largeObject.someProperty;
};
}
const closure = outer();
// 即使outer函数执行完毕,largeObject也不会被回收,因为closure闭包引用了它
- **避免方法**:尽量减少闭包中对不必要对象的引用。如果闭包不需要访问某个对象,可以在适当的时候将其设为`null`,让垃圾回收器能够回收该对象。例如:
function outer() {
let largeObject = { /* 包含大量数据的对象 */ };
return function inner() {
const result = largeObject.someProperty;
largeObject = null; // 切断对largeObject的引用
return result;
};
}
const closure = outer();
- 循环引用
- 场景:两个对象相互引用,形成循环引用,导致垃圾回收器无法回收它们。例如:
function createCircularReference() {
const obj1 = {};
const obj2 = {};
obj1.prop = obj2;
obj2.prop = obj1;
return [obj1, obj2];
}
const circularRefs = createCircularReference();
// 即使circularRefs不再被使用,obj1和obj2也不会被回收,因为它们相互引用
- **避免方法**:在适当的时候打破循环引用。例如,在不再需要这些对象时,手动将循环引用的属性设为`null`。例如:
function createCircularReference() {
const obj1 = {};
const obj2 = {};
obj1.prop = obj2;
obj2.prop = obj1;
return [obj1, obj2];
}
const circularRefs = createCircularReference();
// 不再需要circularRefs时
circularRefs[0].prop = null;
circularRefs[1].prop = null;