js对象在内存中怎样保存
loyalvi Lv7

js对象在内存中怎样保存

js对象在内存中怎样保存

一般来说主要分为两大类方法。一类方法是直接将名称(或者名称的哈希值)和值(可能是实际内容也可能是地址)全部保存进去,这时可以使用类似 JSON 的格式。这种方式使用起来会比较灵活,但是在执行效率上会存在些问题,因为查找属性的过程会比较费时间。另一类方法是使用类似 C 语言中结构体的方式来保存,即只保存值而不保存变量,变量通过偏移量来查找。例如,{width:10,length:15} 这个对象可以直接用 8 个字节来表示, width 的偏移量是 0, length 的偏移量是 4 。这样在使用时不需要查找,直接按偏移量调用,效率就比第一类方法高, C++中的类就采用这种方法。但是, JS 中的对象有些特殊,因为它的对象的属性是不确定的,而且可以随时修改(例如添加新的属性),这对于编程来说会很方便,但是对于按照第二类方法来保存对象数据来说就有点麻烦了。
具体使用哪种方法来保存对象数据是由具体引擎的设计者来定的,不同的引擎可能会采用不同的处理方法。早期的 JS 引擎以第一类方法为主,而新的引擎为了提高效率也有采用第二类方法来保存的。对于第二类方法来说,用同一个 function 创建的实例对象具有相同的结构,这时就可以将其看作同一类型(类似于 C 语言中的同一个结构体)来处理,但所创建的对象自身的属性也是可以修改的,在修改之后就成了新的类型。

扩展

在 JavaScript 中,对象(包括数组、函数等)的内存管理由 JavaScript 引擎(如 V8)自动完成。以下是 JS 对象在内存中的存储方式和工作原理:

1. 内存模型:栈(Stack)和堆(Heap)

  • 栈(Stack)
    • 存储 原始类型的值number, string, boolean, null, undefined, symbol, bigint)和 执行上下文(如函数调用时的局部变量)。
    • 内存分配和回收速度快,但容量有限。
  • 堆(Heap)
    • 存储 引用类型的值(对象、数组、函数等)。
    • 内存分配灵活,容量大,但管理复杂(需要垃圾回收)。

2. 对象的存储方式

  • 对象存储在堆中
    • 当创建一个对象时,JavaScript 引擎会在堆中分配一块内存,存储对象的属性和方法。
    • 栈中仅保存该对象的 引用地址(指针),指向堆中的实际数据。
  • 示例
    1
    2
    let obj = { name: "Alice", age: 25 }; 
    // 栈中存储变量 `obj` 的指针,堆中存储对象内容。

3. 对象的结构

  • 对象在堆中的存储形式
    • 对象在堆中表现为一个 键值对的集合,每个属性名和值都需要占用内存。
    • 如果属性值是另一个对象,则存储的是该对象的引用地址。
  • 嵌套对象的存储
    1
    2
    3
    4
    let person = {
    name: "Bob",
    address: { city: "New York" } // `address` 属性指向另一个堆中的对象
    };

4. 对象的引用与拷贝

  • 引用传递
    • 对象通过引用传递,多个变量可以指向同一个堆内存地址。
    • 修改其中一个变量会影响所有指向同一地址的变量。
    1
    2
    3
    4
    let a = { value: 1 };
    let b = a; // `b` 和 `a` 指向同一内存地址
    b.value = 2;
    console.log(a.value); // 输出 2
  • 深拷贝与浅拷贝
    • 浅拷贝:仅复制对象的引用(如 Object.assign())。
    • 深拷贝:递归复制对象的所有层级(如 JSON.parse(JSON.stringify(obj)))。

5. 垃圾回收(Garbage Collection)

JavaScript 引擎通过垃圾回收机制自动释放不再使用的对象内存。主要算法包括:

  • 标记清除(Mark-and-Sweep)
    • 标记所有从根对象(全局变量、当前执行上下文)可达的对象。
    • 清除未被标记的对象(不可达对象)。
  • 引用计数(Reference Counting)
    • 记录对象被引用的次数,当引用数为 0 时回收内存。
    • 缺点:无法处理循环引用(如两个对象互相引用)。

6. 内存泄漏的常见原因

  • 未释放的引用
    1
    2
    3
    let element = document.getElementById("myElement");
    element.onclick = function() { /* ... */ };
    // 如果删除 DOM 元素但未解除事件绑定,函数和元素可能无法被回收。
  • 全局变量
    1
    2
    3
    function leak() {
    leakedData = "This is a leak"; // 意外创建全局变量
    }
  • 闭包
    1
    2
    3
    4
    5
    6
    7
    function createClosure() {
    let largeData = new Array(1000000).fill("data");
    return function() {
    console.log(largeData.length); // 闭包引用了 `largeData`
    };
    }
    // 即使函数执行完毕,`largeData` 仍被闭包引用,无法释放。

7. 优化对象内存使用

  • 减少全局变量:避免无意中创建全局变量。
  • 及时解除引用:不再使用的对象设为 null
    1
    2
    3
    let data = loadHugeData();
    // 使用完毕后解除引用
    data = null;
  • 避免内存泄漏:手动清理事件监听器、定时器等。
  • 使用弱引用(WeakMap/WeakSet)
    1
    2
    3
    4
    let weakMap = new WeakMap();
    let key = { id: 1 };
    weakMap.set(key, "value");
    // 当 `key` 不再被引用时,WeakMap 中的条目会被自动清除。

8. JS 引擎的优化(以 V8 为例)

  • 隐藏类(Hidden Class)
    • 相同结构的对象共享隐藏类,减少内存占用。
    • 动态添加/删除属性会破坏隐藏类,降低性能。
  • 内联缓存(Inline Caching)
    • 缓存对象属性的访问路径,加速属性查找。

总结

  • 对象存储在堆中,栈中保存其引用地址。
  • 垃圾回收机制自动管理内存,但需警惕内存泄漏。
  • 通过 优化代码习惯使用弱引用 等手段,可以更高效地管理内存。
由 Hexo 驱动 & 主题 Keep
访客数 访问量