JavaScript 对象生命周期

对象从创建到销毁的完整过程

问题

JavaScript 对象从创建到销毁经历了哪些阶段?垃圾回收是如何工作的?

解答

JavaScript 对象生命周期分为三个阶段:创建使用销毁

1. 对象创建

// 字面量创建
const obj1 = { name: 'Alice' };

// 构造函数创建
const obj2 = new Object();
obj2.name = 'Bob';

// Object.create 创建(指定原型)
const proto = { greet() { return 'Hello'; } };
const obj3 = Object.create(proto);

// 类创建
class Person {
  constructor(name) {
    this.name = name;
  }
}
const obj4 = new Person('Charlie');

创建时,引擎会:

  • 在堆内存中分配空间
  • 初始化内部属性(如 [[Prototype]]
  • 执行构造逻辑

2. 对象使用

const user = { name: 'Alice', age: 25 };

// 读取属性
console.log(user.name); // 'Alice'

// 修改属性
user.age = 26;

// 添加属性
user.email = 'alice@example.com';

// 删除属性
delete user.age;

// 遍历属性
for (const key in user) {
  console.log(key, user[key]);
}

3. 对象销毁(垃圾回收)

当对象不再被引用时,会被垃圾回收器回收。

// 引用计数的局限性
function createCycle() {
  const objA = {};
  const objB = {};
  
  // 循环引用
  objA.ref = objB;
  objB.ref = objA;
  
  // 函数结束后,objA 和 objB 互相引用
  // 引用计数无法回收,但标记清除可以
}

createCycle();
// 现代引擎使用标记清除,能正确回收循环引用

标记清除算法

// 模拟标记清除过程
let root = {
  child: {
    grandchild: { value: 1 }
  }
};

// 阶段1:从根对象开始,标记所有可达对象
// root -> child -> grandchild 都被标记

// 断开引用
root.child = null;

// 阶段2:grandchild 不可达,将被回收
// 下次 GC 时,grandchild 对象被清除

手动解除引用

class Cache {
  constructor() {
    this.data = new Map();
  }
  
  set(key, value) {
    this.data.set(key, value);
  }
  
  // 清理方法,帮助 GC
  clear() {
    this.data.clear();
    this.data = null;
  }
}

let cache = new Cache();
cache.set('user', { name: 'Alice' });

// 使用完毕后清理
cache.clear();
cache = null; // 解除引用

WeakMap/WeakSet 的弱引用

// WeakMap 不阻止键对象被回收
const weakMap = new WeakMap();

let obj = { data: 'important' };
weakMap.set(obj, 'metadata');

console.log(weakMap.get(obj)); // 'metadata'

// 解除强引用
obj = null;

// obj 可被 GC 回收,weakMap 中的条目自动消失
// 适合存储对象的附加信息而不影响其生命周期

内存泄漏常见场景

// 1. 意外的全局变量
function leak1() {
  leaked = 'oops'; // 没有声明,成为全局变量
}

// 2. 未清理的定时器
function leak2() {
  const data = { /* 大量数据 */ };
  setInterval(() => {
    console.log(data); // data 永远不会被回收
  }, 1000);
}

// 3. 闭包持有引用
function leak3() {
  const largeData = new Array(1000000);
  return function() {
    // 即使不使用 largeData,闭包仍持有引用
    console.log('hello');
  };
}

// 4. DOM 引用未清理
const elements = [];
function leak4() {
  const div = document.createElement('div');
  elements.push(div); // 即使 DOM 移除,数组仍持有引用
}

关键点

  • 创建阶段:堆内存分配空间,初始化原型链和内部属性
  • 标记清除:现代引擎从根对象遍历,标记可达对象,清除不可达对象
  • WeakMap/WeakSet:弱引用不阻止 GC,适合缓存和元数据存储
  • 内存泄漏:全局变量、未清理的定时器、闭包、DOM 引用是常见原因
  • 手动清理:大对象使用后设为 null,帮助 GC 更快回收