浏览器工作原理与实践
李兵
前盛大创新院高级研究员
56402 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 46 讲
浏览器工作原理与实践
15
15
1.0x
00:00/00:00
登录|注册

08 | 调用栈:为什么JavaScript代码会出现栈溢出?

栈溢出(Stack Overflow)
利用浏览器查看调用栈的信息
JavaScript中的调用栈
栈的概念
函数调用过程
示例代码解释
eval函数
函数执行上下文
全局执行上下文
思考时间
总结
如何利用好调用栈
函数调用
JavaScript代码出现栈溢出
调用栈

该思维导图由 AI 生成,仅供参考

上篇文章中,我们讲到了,当一段代码被执行时,JavaScript 引擎先会对其进行编译,并创建执行上下文。但是并没有明确说明到底什么样的代码才算符合规范。
那么接下来我们就来明确下,哪些情况下代码才算是“一段”代码,才会在执行之前就进行编译并创建执行上下文。一般说来,有这么三种情况:
当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文。
好了,又进一步理解了执行上下文,那本节我们就在这基础之上继续深入,一起聊聊调用栈。学习调用栈至少有以下三点好处:
可以帮助你了解 JavaScript 引擎背后的工作原理;
让你有调试 JavaScript 代码的能力;
帮助你搞定面试,因为面试过程中,调用栈也是出境率非常高的题目。
比如你在写 JavaScript 代码的时候,有时候可能会遇到栈溢出的错误,如下图所示:
栈溢出的错误
那为什么会出现这种错误呢?这就涉及到了调用栈的内容。你应该知道 JavaScript 中有很多函数,经常会出现在一个函数中调用另外一个函数的情况,调用栈就是用来管理函数调用关系的一种数据结构因此要讲清楚调用栈,你还要先弄明白函数调用栈结构
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

JavaScript代码出现栈溢出是因为JavaScript引擎利用调用栈来管理函数调用关系的一种数据结构。文章详细介绍了执行上下文的创建和调用栈的作用,通过示例代码解释了函数调用的过程和调用栈的状态变化。调用栈的重要性和实用性得到了强调,包括如何利用浏览器查看调用栈的信息。通过断点和console.trace()可以查看当前的函数调用关系,对于分析复杂结构代码或检查Bug非常有用。文章内容深入浅出,通过图文并茂的方式生动地解释了调用栈的概念和实际应用,对于想深入了解JavaScript引擎背后的工作原理以及调试JavaScript代码的读者来说,是一篇很有价值的文章。 文章总结了调用栈的基本原理和栈溢出的问题,提出了避免或解决栈溢出的方法。最后,留下了一个思考题,引发读者思考如何优化递归代码以解决栈溢出问题。整体而言,本文以清晰的逻辑和生动的例子,帮助读者深入理解了调用栈的概念和应用,对于想提升JavaScript调试能力和理解引擎内部工作原理的读者具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《浏览器工作原理与实践》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(121)

  • 最新
  • 精选
  • 黄晓杰
    老师,我有一个疑问,调用栈是后进先出,那么当存在闭包时,某个函数的执行上下文还存在,那么其他函数的出栈是否受影响?

    作者回复: 执行上下文已经没了,只不过内部函数引用的变量还保存在堆上,所以不影响栈的操作,后面一节有分析这个问题!

    2019-09-11
    2
    36
  • mfist
    1. 改成尾递归调用(需要在严格模式下面生效) function runStack (n, result=100) { if (n === 0) return result; return runStack( n- 2, result); } runStack(50000, 100) 2. 改成循环调用,不使用递归函数,就不存在堆栈溢出

    作者回复: 有尝试过尾优化会生效吗?

    2019-08-22
    11
    18
  • 关于调用栈的大小,不用的平台,比如浏览器,nodejs 怎么查看设置的,还是硬编码的?

    作者回复: 调用栈有两个指标,最大栈容量和最大调用深度,满足其中任意一个就会栈溢出,不过具体多大和多深,这个没有研究过,你可以拿我留的作业那段代码去各平台测试下,应该很快就能测试出来最大调用深度。

    2019-08-24
    5
    10
  • pyhhou
    思考题当中的函数,如果输入参数是正偶数,那么不管数值多大,最后结果都是 100,除此之外,如果输入参数是负数或者是正奇数,甚至说是浮点数,那么使用递归方式调用会导致栈溢出,使用循环方式去实现会导致死循环,如果仅仅是基于当前的输入参数(50000)改写的话: function runStack (n) { return 100; } runStack(50000); 当然也可以把递归改成循环的写法,但是要注意的是这时的输入参数仅限定于正偶数,否则会死循环: function runStack (n) { while (n !== 0) { n -= 2; } return 100; } runStack(50000);

    作者回复: 可能我的题目出得不太好,误解我出题目的意思了,我的意思是runStack要执行50000次的,但是要避免栈溢出,改成斐波那契数列的列子可能好点。

    2019-08-23
    8
  • 心飞扬
    addAll函数中的result并不在变量环境中,而是执行完add后才被放在this中

    作者回复: 是的

    2019-11-14
    3
    7
  • Claire
    运用尾递归,其实尾递归也会产生栈溢出问题,但是查资料看,很多编译器已经优化了尾递归,当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的

    作者回复: 有测试过吗?这个特性我跟踪了很久,但是是都没效果的,所以这块内容我在课程里面也没提了

    2019-08-23
    7
  • 许童童
    将递归改成迭代就好了,还可以使用尾递归优化。感觉老师这道题改成斐波那契数列会更好。 function runStack (n) { while (n > 0) { n -= 2 } return 100 } runStack(50000)

    作者回复: 是的 改成斐波那契数列会好点。 不过尾部优化似乎是没效果的

    2019-08-22
    5
    7
  • Geek_East
    执行上下问的本质是一个object吗

    作者回复: 执行上下文是一种语义,一种规范,这种如何实现的语言层面不用是不用关心的! JS虚拟机可以用各种不同的方式去实现,只要最终能满足这种语义就可以了! 比如V8就是使用C++来实现的,无非就是如何分配数据,如何查找数据!

    2019-12-06
    3
  • 爱吃锅巴的沐泡
    老师,有两个问题: 1、老师在文中写到“首先,从全局执行上下文中,取出 add 函数代码。”,这里是取到函数的引用,还是整个函数代码,函数的存储是怎样的? 2、声明带参的函数并调用的编译过程是怎样的,参数应该是和arguments有关吧,老师能详细说一下编译过程嘛?

    作者回复: 第一个:看成是一个引用,函数实体是保存到堆中的。堆栈结构后面章节会介绍。 第二个:如果一个函数带有参数,编译过程中,参数会通过参数列表保存到变量环境中

    2019-08-22
    3
  • tick
    老师,我可不可以这样理解,您所说的调用栈并不是严格意义上的栈,因为在addAll中调用add时,add的函数代码还是在全局上下文中,即此时栈中有全局上下文,addAll上下文,但此时还需要去访问栈底的全局上下文中取出add的函数代码,这样是不是不算是严格意义上的栈?

    作者回复: 其实执行上下文也是存放在系统的堆区的,模拟的栈结构,他们和c语言的执行栈还是不一样的

    2019-08-25
    2
    2
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部