What?
用来管理函数调用关系的一种数据结构
调用栈是怎么产生的?
函数的调用过程
- Javascript 引擎在预编译阶段,为全局代码创建全局执行上下文,全局执行上下文包括:
- 变量环境:
- 提升的变量
- 提升后的函数定义
- 词法环境:
- 然后 Javascript 引擎在全局代码的解释执行阶段,执行到函数调用语句时,会自动判断这是函数调用:
- 从全局执行上下文中取出函数内的代码
- 对这段代码重复上面的编译阶段,并创建该函数的执行上下文和可执行代码
- 然后执行这段可执行代码
调用栈因函数调用应运而生
通过分析上述函数的调用过程,可以得出 Javascript 在执行的过程中可能会有多个执行上下文,那么就需要一种 “工具” 去管理这个执行上下文了
什么是栈?
栈是一种只能在一端进行插入和删除操作的特殊线性表数据结构。
- 按先进后出的原则存储数据,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
- 栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针
什么是 Javascript 调用栈?
JavaScript 引擎利用栈结构来管理执行上下文,将执行上下文按照执行顺序压入到的这个栈就够就是 Js 的调用栈(也叫执行上下文栈),是 JavaScript 引擎追踪函数执行的一个机制
利用浏览器查看调用栈信息
/*
1、利用浏览器查看调用栈信息
2、用 console.trace() 打印函数调用关系信息
*/
var a = 2;
function foo(b,c){
console.trace();
debugger;
return b + c;
}
function foo2(b,c){
var d = 10;
result = foo(b,c);
return a + result + d;
}
foo2(3,6);- 打开“开发者工具”,点击“Source”标签,选择 JavaScript 代码的页面,栈的最底部是 anonymous 全局函数入口
- 使用 console.trace() 在控制台输出当前函数的调用关系
什么是栈溢出?
调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript 引擎报错的现象就是栈溢出,调用栈最大栈容量和最大调用深度,满足其中任意一个就会栈溢出
怎么优化栈溢出问题?
- 把递归调用的形式改造成其他形式
- 使用循环代替递归
- 某些环境下使用尾递归,其宿主环境已经做了栈溢出的优化,目前只知道 Safari 版本 12.1.2 (14607.3.9) 支持尾递归优化
- 尾递归是什么?
-- 一个函数中所有递归形式的调用都出现在函数的末尾
- 尾递归的特点?
-- 在回归过程中不用做任何操作
- 尾递归的优化原理?
-- 当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要
- 当前任务拆分为其他很多小任务
/*
优化栈溢出代码
代码分析:
1、该段代码导致栈溢出的原因是什么?
-- 是 foo3 函数使用了递归函数调用自身,当 n 的值超过浏览器最大调用栈时超出的
2、可以根据产生问题的原因可以从下面方案优化
(1)、使用其他方式实现该递归函数,即任务执行过程中不再创建新的执行上下文,只在当前函数执行山下文中重复执行任务
(2)、使用定时器将调用栈的任务拆分为多个定时器的任务
*/
/* 使用 while 循环实现递归函数,取消代码块进调用栈 */
function foo4 (n) {
while(true){
if(n <= 0){
return 'stop';
}
n--;
}
}
// console.log(foo4(12576));
/* 使用定时器将当前任务调用 */
function foo5 (n) {
if (n === 0) {
return 'stop';
};
console.trace();
return setTimeout(()=>{
foo5( n - 1);
});
}
console.log(foo5(12576));