5分钟理解JS调用栈

逻辑解码人
• 阅读 5501

这篇文章内容参考极客时间的浏览器课程(付费)

前言

我们在写代码的过程中,或多或少或者不经意间都会遇到栈溢出的问题,如下:
5分钟理解JS调用栈
为什么会出现这个问题呢?要弄清楚原因,需要先弄清楚调用栈。

调用栈

函数是js中的最高公民,日常编码中函数调用函数是屡见不鲜呐。调用栈就是用来管理函数调用关系的一种数据结构。

函数调用

函数调用太简单,看代码分析

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

分析这段代码的执行过程

  • 编译阶段

我们在上一篇文章中已经介绍过js的执行流程,编译阶段这里就不做详细讲解了,编译结束后会生成:

  1. 全局执行上下文
  2. 可执行代码

如下图:

5分钟理解JS调用栈

  • 执行阶段

生成可执行代码之后,JS引擎开始顺序执行代码,执行到add这里时,JS引擎判断出这里是函数调用,然后执行下面操作:

  1. 从全局上下文中,取出add函数代码
  2. 对add函数的这段代码进行编译(创建该函数的执行上下文环境和可执行代码)
  3. 执行add函数,输出结果

5分钟理解JS调用栈
函数调用完毕,在执行add函数时,会存在两个执行上下文,一个是全局执行上下文,一个是add函数的执行上下文。

那么JS引擎是怎么管理多个执行上下文的呢,JS引擎是通过栈来管理这些执行上下文的。

栈其实很简单啦,本来决定略过,考虑到部分初学者和非科班的朋友,决定还是简单描述一下。

假如现在有一个只能放一本书的盒子和一堆书,把书放入盒子之后再拿出来,那就只能从上往下拿出来了,后面放进去的先拿出来。

栈就是这个盒子,最大的特点——先进后出

调用栈

调用栈就是管理这些执行上下文的栈,就叫调用栈。每次创建好一个执行上下文之后,就会放入调用栈中。

看下边这个例子

var a = 2;
function add(b, c) {
    return b + c;
}

function addAll(b, c) {
    var d = 10;
    var result = add(b, c);
    return a + result + d;
}

addAll(3, 6);

在上面这段代码中,在addAll函数中调用了add函数,现在我们来逐步分析调用栈是如何变化的

  • 第一步,创建全局执行上下文,并将其压入栈底,如下图:

5分钟理解JS调用栈

从图中可以看出,变量a、函数add、函数addAll都保存到全局执行上下文的变量环境对象中。

全局执行上下文环境压入调用栈后,JS引擎开始执行全局代码。

a = 2;

该语句会将全局执行上下文变量环境中a的值设置为2。全局执行上下文环境状态如下图:
5分钟理解JS调用栈

addAll(3, 6);

当调用addAll函数时,JS引擎会编译addAll函数,并为addAll创建一个执行上下文,最后将addAll函数的执行上下文环境压入栈中,如下图:
5分钟理解JS调用栈
addAll函数的执行上下文创建成功之后,接着执行addAll函数的可执行代码。

d = 10;
result = add(b, c);
return a + result + d;

执行到add函数调用语句时,同样会为add函数创建一个执行上下文环境,并将其压入调用栈,如下图所示:
5分钟理解JS调用栈
创建好add函数的执行上下文环境之后,接着执行add函数的可执行代码

return b + c;

add函数返回时,add函数的执行上下文环境就会从调用栈顶部弹出,并将result的值设置为add函数的返回值,也就是9,如下图:
5分钟理解JS调用栈
然后执行addAll函数中的接下来可执行代码

return a + result + d;

这个语句执行完成之后,把结果返回,addAll函数的执行上下文环境也会从调用栈顶部弹出,此时调用栈中就只剩下全局执行上下文了。如下图所示:
5分钟理解JS调用栈
至此,整个JS流程执行结束。

调用栈是JS引擎追踪函数执行的一个机制,当一次有多个函数被调用时,通过调用栈就能够追踪到哪个函数正在被执行以及各个函数之间的调用关系。

开发中,如何利用调用栈
  • 利用浏览器查看调用栈的信息

打开开发者工具(f12) -> source -> 打断点 -> 刷新
就可以通过右边的“call stack”来查看当前的调用栈的情况。如下图:

5分钟理解JS调用栈

从图中可以看出,右边的"call stack"下面显示出来了函数的调用关系:
栈的底部是anonymous,也就是全局的函数入口;中间是addAll函数;顶部是add函数。非常清晰的反应了函数的调用关系。所以在分析复杂的代码时,调用栈是非常有用的。

  • console.trace()

也可以在代码中添加console.trace()来输出函数的调用关系,如在add函数中增加console.trace(),如下图:
5分钟理解JS调用栈

  • 栈溢出

调用栈是用来管理执行上下文的数据结构,先进后出。需要注意的是,它是有大小的,当入栈的执行上下文超过了一定数目,JS引擎就会报错,这种错误就叫做栈溢出。

递归函数,很容易出现栈溢出,如:

function add(a, b) {
    return add(a, b);
}
add(1,2);

当执行时,就会出现栈溢出情况。

分析:
当JS引擎开始执行add函数时,就会为add函数创建执行上下文环境并压入调用栈中,但是,这个函数是递归的并且没有终止条件,所以JS引擎会一直创建新的函数执行上下文,并反复将其压入调用栈中,当超过调用栈的最大限度之后,就会出现栈溢出错误。

理解调用栈、栈溢出之后,平时写代码就可以更好的避免栈溢出的情况出现。

写在最后

这几天有点偷懒啦,坚持输出技术文章,对于我这种手残党,确实是有点难坚持。后续考虑转载好的技术文章,先保证每日一更吧。

欢迎关注我的公众号:匿名程序媛
一起挖坑、填坑
5分钟理解JS调用栈

技术交流qq群:936183824

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
3年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
梦
4年前
微信小程序new Date()转换时间异常问题
微信小程序苹果手机页面上显示时间异常,安卓机正常问题image(https://imghelloworld.osscnbeijing.aliyuncs.com/imgs/b691e1230e2f15efbd81fe11ef734d4f.png)错误代码vardate'2021030617:00:00'vardateT
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
3年前
JVM 字节码指令表
字节码助记符指令含义0x00nop什么都不做0x01aconst\_null将null推送至栈顶0x02iconst\_m1将int型1推送至栈顶0x03iconst\_0将int型0推送至栈顶0x04iconst\_1将int型1推送至栈顶0x05ic
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(