JavaScript调用栈和作用域链指南:新手也能快速上手

作用域链和调用栈是 JavaScript 中非常重要的概念,它们负责管理函数作用域和调用关系。本文将深入探讨作用域链和调用栈的概念、原理以及在实际编程中的应用,帮助读者更好地理解这两个概念。

调用栈

在 JavaScript 中,调用栈是用来管理函数调用关系的一种数据结构。当代码执行到一个函数时,会将该函数的信息(如参数、局部变量)压入调用栈中;当函数执行完成后,会将其从调用栈中弹出。这种先进后出的数据结构使得 JavaScript 可以追踪函数的调用关系,确保代码的执行顺序和上下文的切换。

调用栈的原理

让我们通过下面的代码示例来说明调用栈的原理:

csharp 复制代码
var a = 2

function add(){
    var b = 10
    return a + b
}

add()

来看这个例子,代码执行之前要预编译(有不懂预编译的小伙伴可以去看下这篇文章 《 简单易懂!!超详细带你了解什么是JavaScript预编译!》 ) 编译器编译全局代码的时候会创建一个全局执行上下文对象,而栈就是用来存储一个又一个执行上下文对象的,此时全局执行上下文入栈了:

在全局执行上下文中有一个变量环境和词法环境,简单来说,变量环境中放的是你用var声明的变量,词法环境中放的是你用let和const声明的变量,预编译的时候就会把这些变量放进去:

全局预编译完成后,就开始执行代码,此时a得到值2:

之后执行函数add的调用,执行之前又要先进行它的编译,此时编译器又会创建一个add函数的执行上下文对象,将其压入栈:

编译后执行,b的值变为10:

还要返回a+b的值,所以就要去查找a和b,查找规则是先从本身的词法环境里找,找不到再去本身的变量环境里找,我们在本身的变量环境里找到了b = 10,但是没找到a,就去下一个执行上下文里找(此例中为全局执行上下文),同样先从词法环境里找,找不到再去变量环境里找,得到a = 2,然后就可以返回a+b的值即12,查找方向如图:

这整个红色的框框其实就是调用栈 ,我们用它来管理函数的调用关系,谁先入栈谁后入栈,如何访问a如何访问b, 调用栈能梳理清楚呈现出来。

调用栈的应用

了解调用栈的原理对于理解 JavaScript 中的函数调用、递归等具有重要意义。同时,调用栈也是 JavaScript 引擎控制执行上下文切换的重要工具,对于开发者而言,合理地利用调用栈可以避免栈溢出等问题,提高代码的执行效率。

栈溢出

栈溢出是什么呢?我们看下面的代码:

scss 复制代码
function foo(){
    foo()
}
foo()

首先预编译全局执行上下文入栈:

然后执行函数调用,进行函数预编译又将foo执行上下文入栈:

但是函数foo里面又调用了foo,于是foo又入栈:

如此循环,一个又一个foo执行上下文入栈,而调用栈的空间是有限的,所以最会导致栈溢出。

作用域链

在深入讨论作用域链之前,我们先了解一下 JavaScript 的作用域。JavaScript 中的作用域是指变量和函数的可访问性范围,它由词法环境来管理。关于作用域的详细讲解在我《写给小白的JavaScript作用域及声明提升详解》这篇文章中有。 作用域链则是描述了在执行上下文中变量查找的过程,其本质是通过词法环境来确定某作用域的外层作用域,从内向外逐级查找变量的过程。

作用域链的原理

在 JavaScript 中,每个函数都有自己的作用域,并且可以访问其外部作用域的变量。当代码在一个作用域中无法找到某个变量时,JavaScript 引擎会沿着作用域链向上查找,直至全局作用域。这种链状的查找关系就是作用域链。

让我们通过一个简单的代码示例来说明作用域链的原理:

csharp 复制代码
function bar (){
    console.log(myName);
}

function foo(){
    var myName = '涛哥'
    bar()
}

var myName = '万总'

foo()

我们先把它的调用栈画出来:

但其实调用栈的变量环境里面还有一个内置的outer属性,全局执行上下文里面的outer值为null,foo里的outer会指向全局,bar里的outer也指向全局,outer的指向是它所在的执行上下文所处的词法环境,即它所处的作用域,而foo和bar外层的作用域都为全局:

而我们执行这段代码,调用foo,foo里面又调用了bar,执行输出myName的值为'万总',查找myName,bar本身内部是没有的,之后往外去哪查找,是看outer的指向,bar的outer指向全局执行上下文,所以去那里面查找,得到myName的值为'万总'。

其实所谓的作用域链就是作用域的链状查找关系,通过词法环境来确定某作用域的外层作用域,查找变量由内而外的这种链状关系,就叫做作用域链。

作用域链的应用

了解作用域链的原理对于理解闭包、作用域的嵌套关系以及变量的查找具有重要意义。在实际编程中,合理利用作用域链可以更好地管理变量和函数,避免命名冲突,提高代码的可维护性。

调用栈和作用域链到这里就讲完啦,希望看完这篇文章后能帮助你们更好的理解这两个概念ヾ(◍°∇°◍)ノ゙

相关推荐
过期的H2O25 分钟前
【H2O2|全栈】关于CSS(4)CSS基础(四)
前端·css
纳尼亚awsl18 分钟前
无限滚动组件封装(vue+vant)
前端·javascript·vue.js
八了个戒24 分钟前
【TypeScript入坑】TypeScript 的复杂类型「Interface 接口、class类、Enum枚举、Generics泛型、类型断言」
开发语言·前端·javascript·面试·typescript
西瓜本瓜@26 分钟前
React + React Image支持图像的各种转换,如圆形、模糊等效果吗?
前端·react.js·前端框架
黄毛火烧雪下27 分钟前
React 的 useEffect 钩子,执行一些异步操作来加载基本信息
前端·chrome·react.js
蓝莓味柯基32 分钟前
React——点击事件函数调用问题
前端·javascript·react.js
资深前端之路33 分钟前
react jsx
前端·react.js·前端框架
cc蒲公英1 小时前
vue2中使用vue-office库预览pdf /docx/excel文件
前端·vue.js
一嘴一个橘子1 小时前
js 将二进制文件流,下载为excel文件
javascript
Sam90291 小时前
【Webpack--013】SourceMap源码映射设置
前端·webpack·node.js