JavaScript进阶必学之作用域、GC 垃圾回收、闭包详解

 更新时间:2026年05月07日 10:05:49   作者:A923A  
在编程语言中内存管理是一个非常重要的方面,JavaScript 作为一种高级语言,开发者不需要手动管理内存的分配和释放,这篇文章主要介绍了JavaScript进阶必学之作用域、GC 垃圾回收、闭包的相关资料,需要的朋友可以参考下

前言:

本文系统讲解JavaScript核心知识,涵盖作用域、作用域链、垃圾回收、闭包与变量提升。结合案例拆解底层原理,通俗易懂梳理关键概念与应用场景,夯实前端基础,助力高效理解JS底层逻辑。

一:作用域

介绍:

作用域(scope)规定了变量能够被访问的 “范围”,离开了这个 “范围” 变量便不能被访问

作用域分为:

局部作用域
全局作用域

局部作用域

局部作用域又分为函数作用域和块作用域。

函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。

<script>
function getSum() {
  // 函数内部是函数作用域 属于局部变量
  const num = 10
}
console.log(num) // 此处报错 函数外部不能使用局部作用域变量
</script>

总结:

函数内部声明的变量,在函数外部无法被访问
函数的参数也是函数内部的局部变量
不同函数内部声明的变量无法互相访问
函数执行完毕后,函数内部的变量实际被清空了

块作用域:

在 JavaScript 中使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】(当是var的时候可以被访问)无法被访问。

for (let t = 1; t <= 6; t++) {
  // t 只能在该代码块中被访问
  console.log(t) // 正常
}
// 超出了 t 的作用域
console.log(t) // 报错

let 声明的变量会产生块作用域,var 不会产生块作用域
const 声明的常量也会产生块作用域
不同代码块之间的变量无法互相访问
推荐使用 let 或 const

全局作用域

<script> 标签和 .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问。

<script>
// 全局作用域
// 全局作用域下声明了 num 变量
const num = 10
function fn() {
  // 函数内部可以使用全局作用域的变量
  console.log(num)
}
// 此处全局作用域
</script>

注意:

为 window 对象动态添加的属性默认也是全局的,不推荐!

函数中未使用任何关键字声明的变量为全局变量,不推荐!!!

尽可能少的声明全局变量,防止全局变量被污染

二:作用域链

作用域链本质上是底层的变量查找机制。

在函数被执行时,会优先查找当前函数作用域中查找变量

如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域

<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
  let a = 1
  // 局部作用域
  function g() {
    a = 2
    console.log(a)
  }
  g() // 调用g
}
f() // 调用f
</script>

总结:

嵌套关系的作用域串联起来形成了作用域链

相同作用域链中按着从小到大的规则查找变量

子作用域能够访问父作用域,父级作用域无法访问子级作用域

三:垃圾回收机制

垃圾回收机制 (Garbage Collection) 简称 GC

JS 中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收

内存的生命周期

JS 环境中分配的内存,一般有如下生命周期:

内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存

内存使用:即读写内存,也就是使用变量、函数等

内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存

// 为变量分配内存
const age = 18

// 为对象分配内存
const obj = {
  age: 19
}

// 为函数分配内存
function fn() {
  const age = 18
  console.log(age)
}

说明:

全局变量一般不会回收(关闭页面回收)

一般情况下局部变量的值,不用了,会被自动回收掉

内存泄漏:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏

拓展 - JS 垃圾回收机制 - 算法说明

堆栈空间分配区别:

栈(操作系统):由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。

堆(操作系统):一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。

引用计数法

IE 采用的引用计数算法,定义 “内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象

算法:

跟踪记录被引用的次数

如果被引用了一次,那么就记录次数 1,多次引用会 ++

如果减少一个引用就减 1 –

如果引用次数是 0,则释放内存

但它却存在一个致命的问题:嵌套引用(循环引用)

如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄漏。

function fn() {
  let o1 = {}
  let o2 = {}
  o1.a = o2
  o2.a = o1
  return '引用计数无法回收'
}
fn()

因为他们的引用次数永远不会是 0。这样的相互引用如果说很大量的存在就会导致大量的内存泄漏。

标记清除法

标记清除算法将 “不再使用的对象” 定义为 “无法达到的对象”。

就是从根部(在 JS 中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。

那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

解决内存泄漏问题

四:闭包

概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域

简单理解:闭包 = 内层函数 + 外层函数的变量

function outer() {
  const a = 1
  function f() {
    console.log(a)
  }
  f()
}
outer()

闭包应用:实现数据的私有

比如,我们要做个统计函数调用次数,函数调用一次,就 ++

let count = 1
function fn() {
  count++
  console.log(`函数被调用${count}次`)
}
fn() // 2
fn() // 3

但是,这个 count 是个全局变量,很容易被修改

function fn() {
  let count = 1
  function fun() {
    count++
    console.log(`函数被调用${count}次`)
  }
  return fun
}
const result = fn()
result() // 2
result() // 3

这样实现了数据私有,无法直接修改 count

五:变量提升

变量提升是 JavaScript 中比较 “奇怪” 的现象,它允许在变量声明之前即被访问(仅存在于 var 声明变量)

注意:

变量在未声明即被访问时会报语法错误

变量在 var 声明之前即被访问,变量的值为 undefined

let/const 声明的变量不存在变量提升

变量提升出现在相同作用域当中

实际开发中推荐先声明再访问变量

<script>
// 访问变量 str
console.log(str + 'world!')
// 声明变量 str
var str = 'hello '
</script>

最后:

到此这篇关于JavaScript进阶必学之作用域、GC 垃圾回收、闭包的文章就介绍到这了,更多相关JS作用域、GC 垃圾回收、闭包内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论