首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 函数式vs.面向对象:响应未知和不确定
02 | 如何通过闭包对象管理程序中状态的变化?
03 | 如何通过部分应用和柯里化让函数具象化?
04 | 如何通过组合、管道和reducer让函数抽象化?
05|map、reduce和monad如何围绕值进行操作?
06 | 如何通过模块化、异步和观察做到动态加载?
07 | 深入理解对象的私有和静态属性
08|深入理解继承、Delegation和组合
09|面向对象:通过词法作用域和调用点理解this绑定
10|JS有哪8种数据类型,你需要注意什么?
11|通过JS引擎的堆栈了解闭包原理
12|JS语义分析该用迭代还是递归?
13 | JS引擎如何实现数组的稳定排序?
14 | 通过SparkPlug深入了解调用栈
15 | 如何通过哈希查找JS对象内存地址?
16 | 为什么环形队列适合做Node数据流缓存?
17 | 如何通过链表做LRU/LFU缓存?
18 | TurboFan如何用图做JS编译优化?
19 | 通过树和图看如何在无序中找到路径和秩序
20 | 算法思想:JS中分治、贪心、回溯和动态规划
21 | 创建型:为什么说Redux可以替代单例状态管理
22|结构型:Vue.js如何通过代理实现响应式编程
23 | 结构型:通过jQuery看结构型模式
24 | 行为型:通过观察者、迭代器模式看JS异步回调
25 | 行为型:模版、策略和状态模式有什么区别?
26|特殊型:前端有哪些处理加载和渲染的特殊“模式”?
27|性能:如何理解JavaScript中的并行、并发?
28|性能:通过Orinoco、Jank Busters看垃圾回收
29|网络:从HTTP/1到HTTP/3,你都需要了解什么?
30|安全:JS代码和程序都需要注意哪些安全问题?
31|测试(一):开发到重构中的测试
32|测试(二):功能性测试
33|测试(三):非功能性测试
34|静态类型检查:ESLint语法规则和代码风格的检查
35|Flow:通过Flow类看JS的类型检查
36|包管理和分发:通过NPM做包的管理和分发
37|编译和打包:通过Webpack、Babel做编译和打包
38|语法扩展:通过JSX来做语法扩展
39|Polyfill:通过Polyfill让浏览器提供原生支持
40|微前端:从MVC贫血模式到DDD充血模式
41|大前端:通过一云多端搭建跨PC/移动的平台应用
42|元编程:通过Proxies和Reflect赋能元编程
当前位置:
首页>>
技术小册>>
JavaScript进阶实战
小册名称:JavaScript进阶实战
### 第十一章:通过JS引擎的堆栈了解闭包原理 在JavaScript的广阔世界中,闭包(Closure)是一个既强大又深奥的概念,它不仅是函数式编程的基石,也是实现高级功能如数据封装、模块化、私有变量等的关键机制。要深入理解闭包,我们不得不揭开JavaScript引擎内部运作的神秘面纱,特别是其堆栈(Stack)和执行上下文(Execution Context)的管理方式。本章将带领读者通过探索JS引擎的堆栈机制,来揭示闭包背后的原理。 #### 1. JavaScript引擎的堆栈概述 JavaScript引擎执行代码时,会使用两种主要的数据结构来管理内存和执行流程:堆(Heap)和栈(Stack)。 - **堆(Heap)**:用于存储对象(Objects)、数组(Arrays)等复杂数据类型的内存区域。堆内存分配是动态的,且大小不固定,生命周期由垃圾回收机制管理。 - **栈(Stack)**:一种后进先出(LIFO)的数据结构,用于存储代码执行过程中的基础数据,如函数调用的记录、变量等。每当函数被调用时,一个新的执行上下文(Execution Context)就会被创建并推入调用栈中;函数执行完成后,其上下文被销毁并从栈中弹出。 #### 2. 执行上下文与作用域链 每个执行上下文都包含几个关键部分:变量对象(Variable Object,在ES6后更名为Lexical Environment)、作用域链(Scope Chain)、this值等。作用域链是JavaScript实现变量查找的重要机制,它决定了在当前执行上下文中如何访问变量。 - **作用域链**:当JavaScript引擎需要查找一个变量时,它会首先在当前执行上下文的变量对象中查找;如果未找到,则沿着作用域链向上(即向父级作用域)继续查找,直到找到该变量或到达全局作用域为止。 #### 3. 闭包的形成 闭包的形成,本质上是一个函数能够记住并访问其词法作用域(Lexical Scope)中的变量,即使该函数在其词法作用域之外执行。这得益于JavaScript的作用域链机制以及函数对象对词法环境的引用。 - **词法作用域**:函数定义时的作用域,即函数在哪里被定义就决定了其词法作用域。 - **闭包的核心**:当一个函数内部定义了另一个函数,并且内部函数引用了外部函数作用域中的变量时,就形成了闭包。此时,外部函数执行完毕后,其执行上下文虽然会被销毁,但由于内部函数仍然保持着对外部函数作用域中变量的引用,这些变量并不会被垃圾回收,从而实现了数据的封装和持久化。 #### 4. 堆栈视角下的闭包 为了更直观地理解闭包,我们可以从堆栈的角度来考察其运作过程。 假设有以下代码: ```javascript function outer() { let x = 1; function inner() { console.log(x); } return inner; } const myClosure = outer(); myClosure(); // 输出: 1 ``` 1. **outer函数被调用**:此时,outer函数的执行上下文被创建并推入调用栈中。在这个上下文中,变量x被定义并初始化为1,同时内部函数inner也被定义。 2. **inner函数被返回**:当outer函数执行到return语句时,它并没有返回x的值,而是返回了inner函数的引用。重要的是,这个引用保持了对outer函数执行上下文中变量x的引用。 3. **outer函数执行上下文被销毁**:通常情况下,一旦函数执行完成,其执行上下文就会从调用栈中弹出并被销毁,同时其中的变量也会被回收。但由于inner函数仍持有对outer函数作用域中x的引用,因此这个作用域(包括变量x)不会被销毁。 4. **myClosure函数被调用**:当myClosure(即inner函数的引用)被调用时,虽然它并不在自己的词法作用域内(即outer函数的执行上下文已经不在调用栈上),但它依然能够访问到x的值,因为闭包允许它沿着作用域链向上查找,直到找到x为止。 #### 5. 闭包的应用与注意事项 闭包在JavaScript中有着广泛的应用,包括但不限于: - **数据封装**:通过闭包可以隐藏内部变量,只暴露特定的接口来访问或修改这些数据。 - **模块化**:利用闭包可以模拟私有变量和方法的模块,增强代码的组织性和封装性。 - **回调函数与异步编程**:闭包允许回调函数访问定义在外部函数中的变量,这对于处理异步操作尤为重要。 然而,闭包也带来了一些潜在的问题,如内存泄漏(如果闭包中的变量不再需要但仍被引用,会导致这些变量无法被垃圾回收)和性能问题(过多的闭包会增加作用域链的长度,影响变量查找的效率)。 #### 6. 结论 通过深入理解JavaScript引擎的堆栈机制以及作用域链的工作原理,我们得以窥见闭包这一强大特性的本质。闭包不仅是JavaScript语言特性的重要组成部分,更是实现高级编程范式和复杂应用的关键工具。在编写高效、可维护的JavaScript代码时,合理利用闭包将极大地提升代码的灵活性和可复用性。同时,我们也应警惕闭包可能带来的问题,通过良好的编码习惯和性能优化策略来避免潜在的陷阱。
上一篇:
10|JS有哪8种数据类型,你需要注意什么?
下一篇:
12|JS语义分析该用迭代还是递归?
该分类下的相关小册推荐:
剑指javascript
JavaScript面试指南
Javascript编程指南
javascript设计模式原理与实战
经典设计模式Javascript版
Flutter核心技术与实战
npm script实战构建前端工作流
编程入门课:Javascript从入门到实战
剑指javascript-ES6
Node.js 开发实战
深入学习前端重构知识体系
KnockoutJS入门指南