JavaScript 内存机制与闭包解析

JavaScript 内存机制与闭包解析

JavaScript 作为一门动态弱类型语言,其内存管理机制和闭包特性是理解 JS 运行原理的核心。本文将结合具体代码示例,深入解析 JS 的内存机制以及闭包的工作原理。

一、JavaScript 内存空间划分

JavaScript 的内存空间主要分为三大类:

  1. 代码空间:用于存储执行的代码
  2. 栈内存:主要存储简单数据类型和引用类型的地址
  3. 堆内存:主要存储复杂数据类型(对象等)

栈内存的特点是操作速度快、大小固定、存储连续,适合存储体积小 的数据;而堆内存空间大,可存储大型复杂对象,但分配和回收内存相对耗时。

二、数据类型的内存存储

1. 简单数据类型的存储

简单数据类型(如数字、字符串、布尔值等)直接存储在栈内存中,赋值时会进行值拷贝:

ini 复制代码
// 调用栈在栈内存中
// 体积小
function foo() {
    var a = 1; // 赋值,直接存储在栈内存
    var b = a; // 拷贝,在栈内存中创建新值
    a = 2;
    console.log(a); // 2
    console.log(b); // 1
}
foo();

上述代码中,ab是相互独立的变量,修改a的值不会影响b,因为它们在栈内存中占据不同的空间。

2. 复杂数据类型的存储

复杂数据类型(如对象)在栈内存中存储的是指向堆内存的地址,赋值时进行的是引用拷贝:

css 复制代码
function foo() {
    var a = {name:"极客时间"}; // 栈内存存储地址,堆内存存储对象
    var b = a; // 引用式拷贝,b与a指向堆中同一个对象
    a.name = "极客邦";
    console.log(a); // {name: "极客邦"}
    console.log(b); // {name: "极客邦"}
}
foo();

这里ab在栈内存中存储的是同一个地址,指向堆内存中的同一个对象,因此修改a的属性会影响b

三、JavaScript 的动态弱类型特性

JS 作为动态弱类型语言,变量的类型可以在运行过程中动态改变,不需要预先声明类型:

javascript 复制代码
// JS 是动态弱类型语言
var bar; 
console.log(typeof bar); // undefined
bar = 12; // 变为number类型
console.log(typeof bar); // number
bar = '极客时间'; // 变为string类型
console.log(typeof bar); // string
bar = true; // 变为boolean类型
console.log(typeof bar); // boolean
bar = null;
console.log(typeof bar);  // Object (JS设计的bug)
bar = {name:'极客时间'}  // 变为Object类型
console.log(typeof bar); // Object

四、执行上下文与调用栈

JavaScript 引擎通过调用栈来管理执行上下文,每个函数执行时都会创建一个执行上下文,包含:

  • 变量环境
  • 词法环境
  • outer(词法作用域链)
  • this

执行上下文的切换通过调整调用栈的栈顶指针来实现,这也是栈内存需要高效操作的原因。

五、闭包的内存机制

闭包是指内部函数可以访问外部函数作用域中变量的特性,其实现依赖于特殊的内存管理机制。

闭包示例解析

javascript 复制代码
function foo() {
    var myName = "极客时间"
    let test1 = 1
    const test2 = 2
    var innerBar = { 
        setName:function(newName){
            myName = newName
        },
        getName:function(){
            console.log(test1)
            return myName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName() // 输出1
console.log(bar.getName()) // 输出1和"极客邦"

闭包的内存工作原理

  1. 编译阶段 :JS 引擎编译foo函数时,会扫描内部函数(setNamegetName
  2. 识别闭包 :发现内部函数引用了外部函数的变量(myNametest1),判断形成闭包
  3. 创建闭包对象 :在堆内存中创建一个closure(foo)对象,用于保存被内部函数引用的外部变量
  4. 维持引用 :当foo函数执行完毕后,虽然其执行上下文出栈,但由于bar仍引用着内部函数,而内部函数又引用着closure(foo),所以这些变量不会被回收
  5. 访问变量 :内部函数通过引用closure(foo)来访问外部函数的变量

六、内存管理的特点

  • JavaScript 不需要手动管理内存(不同于 C/C++ 需要使用mallocfree
  • 栈内存的回收通过调整栈指针实现,效率高
  • 堆内存的回收通过垃圾回收机制实现,当对象没有任何引用时会被回收
  • 闭包会延长变量的生命周期,可能导致内存占用增加,需要合理使用
相关推荐
JieE2126 小时前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
冬奇Lab8 小时前
AI Workflow 定义的四次演进:从 Markdown 到 JS 脚本,再到分布式多 Agent
javascript·人工智能·agent
一颗烂土豆14 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
kyriewen16 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
weedsfly19 小时前
迭代器、生成器与异步迭代——让数据“按需流动”的艺术
前端·javascript
假如让我当三天老蒯19 小时前
前端跨域解决方案(学习用)
前端·javascript·面试
铁皮饭盒21 小时前
Bun 哪比 Node.js 快?
javascript·后端
JieE2121 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
candyTong1 天前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
_柳青杨1 天前
深入理解 JavaScript 事件循环
前端·javascript