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

1,021 阅读5分钟

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

调用栈

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

调用栈的原理

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

var a = 2

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

add()

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

image.png

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

image.png

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

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

image.png

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

image.png

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

image.png

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

调用栈的应用

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

栈溢出

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

function foo(){
    foo()
}
foo()

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

image.png

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

image.png

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

image.png

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

作用域链

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

作用域链的原理

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

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

function bar (){
    console.log(myName);
}

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

var myName = '万总'

foo()

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

image.png

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

image.png

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

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

作用域链的应用

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

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