• 调用栈:为什么Javascript代码会出现栈溢出


    在这里插入图片描述

    前阵子,在极客时间里面看书,发现了一本宝藏书《浏览器工作原理与实践》——李兵老师所写的。看的我热血沸腾,一下子把我哪些一知半解的知识给通了,太赞了,也在这里给大家分享几篇。

    🎯前期知识

    当一段代码被执行时,JavaScript引擎先会对其进行编译,并且创建执行上下文,分三种情况:
    1、当JavaScript执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份;
    2、当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文也会被销毁;
    3、当使用eval函数的时候,eval的代码也会被编译,并且创建eval执行上下文

    🎯有了这些前期知识之后,一起来聊聊调用栈是个什么?

    比如你在写JavaScript代码的时候,有时可能会遇到栈溢出的错误,如下图所示:
    栈溢出的错误
    那为什么会出现这种错误?这就涉及到了调用栈的内容。你应该知道JavaScript中有很多函数,经常会出现在一个函数中调用另外一个函数的情况,调用栈就是用来管理函数调用关系的一种数据结构。因此要讲清楚调用栈,你还得要弄明白函数调用栈结构

    🥇什么是函数调用

    函数调用就是运行一个函数,具体使用方法是使用函数名称跟着一对小括号。

            var a = 2;
            function add() {
                var b = 10;
                return a+b;
            }
            add()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个代码很简单,先是创建了一个add函数,接着在代码的最下面又调用了该函数。

    那么下面我们就利用这段简单的代码来解释下函数调用的过程。

    在执行到函数add()之前,JavaScript会引擎会为上面这段代码创建全局执行上下文,包含了声明的函数和变量,你可以参考下图:
    全局执行上下文
    执行上下文准备好之后,便开始执行全局代码,当执行到add()这儿时,JavaScript判断这是一个函数调用,那么将执行以下操作:

    • 首先,从全局执行上下文中,取出add函数代码
    • 其次,对add函数这段代码进行编译,并创建该函数的执行上下文可执行代码
    • 最后,执行代码,输出结果

    就这样,当执行到add函数的时候,我们就有了两个执行上下文了——全局执行上下文和add函数的执行上下文。也就是说在执行JavaScript时,可能会存在多个执行上下文,那么JavaScript引擎是如何来执行上下文呢?
    ——答案:通过一种叫栈的数据解构来管理的。那什么是栈呢?它又是如何管理这些执行上下文呢?

    🥈什么是栈

    关于栈,你可以结合生活中的叠盘子来理解,先放的的盘子被压在最后,最先放的盘子在最上面,因此像这种结构就可以被理解为是,具有先进后出的结构。
    栈示意图

    🥉什么是JavaScript的调用栈

    JavaScript 引擎正是利用栈的这种结构来管理执行上下文的。在执行上下文创建好后,
    JavaScript 引擎会将执行上下文压入栈中,通常把这种用来管理执行上下文的栈称为执行
    上下文栈,又称调用栈

    为了更好的理解调用栈,下面来看下个稍微复杂点的示例代码:
    在这里插入图片描述

    在上面这段代码中,你可以看到它是在addAll函数中调用了add函数,那在整个代码的执行过程中,调用栈是怎么变化的呢?

    第一步:创建全局上下文,并将其压入栈底
    全局执行上下文压栈
    全局执行上下文压入到调用栈后,JavaScript 引擎便开始执行全局代码了。首先会执行
    a=2 的赋值操作,执行该语句会将全局上下文变量环境中 a 的值设置为 2。

    第二步:调用addAll函数,创建一个函数执行上下文,最后将该函数的执行上下文压入栈中
    执行addAll函数时的调用栈
    addAll 函数的执行上下文创建好之后,便进入了函数代码的执行阶段了,这里先执行的是
    d=10 的赋值操作,执行语句会将 addAll 函数执行上下文中的 d 由 undefined 变成了10

    第三步:当执行到add函数调用语句时,同样会为其创建执行上下文,并将其压入调用栈
    执行add函数时的调用栈
    当 add 函数返回时,该函数的执行上下文就会从栈顶弹出,并将 result 的值设置为 add
    函数的返回值,也就是 9。紧接着 addAll 执行最后一个相加操作后并返回,addAll 的执行上下文也会从栈顶部弹出,此时调用栈中就只剩下全局上下文了。
    addAll函数执行结束时的调用栈

    🎯栈溢出

    现在你知道了调用栈是一种用来管理执行上下文的数据结构,符合后进先出的规则。不过你要注意,调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript引擎就会报错,我们把这种错误叫做栈溢出。比如下面这段代码,就会出现栈溢出情况
    在这里插入图片描述
    在这里插入图片描述
    从上图可以看到,抛出的错误信息为:超过了最大栈调用大小。那为什么会出现这个问题呢?
    这是因为当 JavaScript 引擎开始执行这段代码时,它首先调用函数 division,并创建执行上下文,压入栈中;然而,这个函数是递归的,并且没有任何终止条件,所以它会一直创建新的函数执行上下文,并反复将其压入栈中,但栈是有容量限制的,超过最大数量后就会出现栈溢出的错误。

    理解了栈溢出原因后,你可以使用一些方法来避免或者解决栈溢出的问题,比如把递归调用的形式改造成其他形式,或者使用加入定时器的方法来把当前任务拆分为其他很多小任务。

    🏆总结

    这里附上我当时看完这篇文章做的思维导图
    在这里插入图片描述

  • 相关阅读:
    通过 saltstack 批量更新 SSL 证书
    【机器学习】034_多层感知机Part.2_从零实现多层感知机
    redis6
    Postman之接口测试
    【无标题】
    MFC中利用CDockablePane实现悬浮窗
    让你的聊天气泡丰富多彩
    zabbix监控告警邮箱提醒,钉钉提醒
    uwsgi配置Django(在树莓派系统linux)
    Python装饰器ZERO 2 HERO
  • 原文地址:https://blog.csdn.net/qq_48701993/article/details/126385889