搞懂作用域链与闭包:JS底层逻辑变简单

JS底层小揭秘:作用域链与闭包,代码+图解一看就懂

在 JavaScript 的学习过程中,理解其底层运行机制是进阶的关键,而作用域链和闭包更是其中的核心概念。,很多人只停留在"会用",没搞懂底层逻辑。本文结合代码+调用栈图解,从V8引擎的运行机制出发,拆解这两个概念的本质,帮你从底层视角搞懂 JS 的执行规则。

一、先搭好JS底层的基础框架

JS代码能运行,依赖V8引擎的三个核心模块:

  1. 调用栈 :分编译阶段 (处理变量/函数提升)和执行阶段(创建执行上下文并压入栈,执行完弹出);
  2. 执行上下文:全局执行上下文(始终在栈底)+ 函数执行上下文(函数调用时创建);
  3. 作用域 :定义变量的查找范围和生命周期,包含let/const块级作用域 (依托栈结构的词法环境),以及var变量提升特性。

二、作用域链:静态的变量查找路径

作用域链(词法作用域链)的核心是:它由函数声明的位置决定,编译阶段就固定了,和调用顺序无关

案例1:为什么bar里的myName取全局值?

对应代码与图示:

javascript 复制代码
function bar(){
  console.log(myName); // 输出"极客时间"
}
function foo() {
  var myName = '极客邦'
  bar() // 在foo内部调用bar
}
var myName = '极客时间'
foo();
运行逻辑拆解:
  1. 编译阶段:barfoo被声明在全局作用域,因此它们的作用域链默认"自身→全局";

  2. 执行阶段:

    1. foo调用时,创建foo执行上下文(变量环境包含myName="极客邦")并压入栈;
    2. foo内部调用bar,创建bar执行上下文并压入栈;
    3. bar中查找myName:自身变量环境无→通过outer指向的全局执行上下文查找→取全局的"极客时间"

案例2:块级作用域下的变量查找

对应代码与图示:

javascript 复制代码
function bar () {
  var myName = '极客世界';
  let test1 = 100;
  if (1) {
    let myName = "Chrome 浏览器";
    console.log(test); 
  }
}
function foo() {
  var myName = '极客邦';
  let test = 2;
  {
    let test = 3;
    bar();
  }
}
var myName = '极客时间';
let myAge = 10;
let test = 1;
foo();
查找过程:
  1. bar内部if块的console.log(test),先查自身块级词法环境 (只有myName="Chrome 浏览器")→ 没找到;
  2. bar函数的词法环境(有test1=100)→ 没找到;
  3. bar的变量环境(有myName="极客世界")→ 没找到;
  4. 查全局执行上下文的词法环境(有test=1)→ 但由于bar的作用域链是"自身块级→bar函数→全局",运行中会输出1

三、闭包:函数"背着"变量的专属背包

闭包是词法作用域的延伸------外部函数执行后,其内部变量被嵌套函数引用,因此不会被垃圾回收,形成一个"变量背包"供嵌套函数使用

案例:闭包的形成与运行

对应代码与图示:

javascript 复制代码
function foo() {
  var myName = '极客时间'
  let test1 = 1
  const test2 = 2
  var innerBar = {
    getName: function() {
      console.log(test1) // 输出1
      return myName
    },
    setName: function(newName) {
      myName = newName // 修改foo内部的myName
    }
  }
  return innerBar
}

var bar = foo() // foo执行上下文出栈
bar.setName("极客邦")
console.log(bar.getName()); // 输出1 + "极客邦"
闭包运行流程:
  1. foo执行时 :创建执行上下文,变量环境存储myName="极客时间",词法环境存储test1=1test2=2,并压入栈;

  2. foo返回innerBar后 :foo执行上下文出栈,但getName/setName引用了myNametest1,这两个变量被保留在内存中(形成闭包,即"专属背包");

  3. 调用bar.setName/getName时

    1. setName执行时,通过闭包找到myName并修改为"极客邦"
    2. getName执行时,通过闭包找到test1并输出1,同时返回修改后的myName

四、核心总结

  1. 作用域链是静态的:由函数声明位置决定,编译阶段固定,和调用顺序无关;
  2. 闭包是词法作用域的延伸:嵌套函数引用外部函数变量,导致外部函数变量不被回收,形成"变量背包";
  3. 底层逻辑的关键:理解调用栈、执行上下文、作用域的关系,是搞懂JS变量查找和内存管理的基础。

JavaScript 的底层运行机制中,词法作用域是基础,它决定了作用域链的静态查找规则,而闭包则是词法作用域的延伸,通过保留自由变量实现了函数对外部作用域的持久访问。理解这些概念,不仅能帮助我们写出更符合 JS 运行逻辑的代码,还能解决实际开发中变量作用域、内存泄漏等常见问题。掌握作用域链与闭包,是深入理解 JavaScript 语言特性的重要一步。

相关推荐
东风破_3 小时前
danci 项目(三):从 JSON 数据到 AI Coding,真实项目里的数据清洗、Prompt 和工程规范
前端·后端·node.js
东风破_4 小时前
danci 项目(一):从需求到架构,一个单词学习系统为什么会这样设计
前端·后端·node.js
蒲公英eric4 小时前
从客户端到服务端:DVWA DOM 型 XSS 模块完整漏洞分析教程
前端·web安全·ai·xss·dvwa·ai安全
单线程_014 小时前
从案例分析 Vue3 Tokenizer+Parser 源码三
前端·javascript·vue.js
顶点多余5 小时前
那些在算法中适合巩固的知识点---1
java·前端·算法
Setsuna_F_Seiei5 小时前
前端转型 Agent 开发 03 之 Agent Tools - 给 Agent 装上手脚
前端·agent·ai编程
jay神5 小时前
【计算机毕业设计】基于SpringBoot的程序教学辅助系统
java·前端·vue.js·spring boot·后端·毕业设计·课程设计
小磊哥er6 小时前
深入解构Claude Code - 第 8 篇 · 数据放哪、钱怎么算
javascript·ai编程
kyriewen8 小时前
我装了30多个Skill,给AI安排了8个岗位
前端·javascript·ai编程
风骏时光牛马8 小时前
大模型落地实践中的技术选型思路与方案对比
前端