调用栈
1.什么是调用栈
在调用一个函数的时候,需要进入另外一个环境(函数)去执行它,执行完了再返回来
调用栈实际就是记录进入一个函数后,回去应该回到什么地方
就类似于打游戏存档读档
- JS引擎在调用一个函数前
- 需要把函数所在的环境推
push
到一个数组里 - 这个数组叫做调用栈
- 等函数执行完了,就会把环境弹(pop)出来
- 然后return到之前的环境,继续执行后续代码
2.举例
console.log(1)
console.log('1+2的结果为'+add(1,2))
console.log(2)
- 由于每次进入一个函数都要记下来等会回到哪里
- 所以把回到的地址写到栈里面
- 如果进入一个函数还要进入一个函数,又把地址放到栈里面
- 函数执行完了就弹栈,把地址弹出去,让函数执行结果根据地址回到相应位置
爆栈
如果调用栈压入的帧过多,程序就会崩溃
1.递归函数
1.1如果使用递归函数,那么很有可能把栈压满
- 阶乘
function f(n){
return n !== 1 ? n*f(n-1) : 1//n不等于1,成立则 n*f(n-1),否则为1
}
- 理解递归:先层层递进计算,得到值后一层一层回归
注意:不是调用自己就是递归,有时候会陷入死循环,而递归是先递进再回归
当n=4
1.2 递归的调用栈
- 递进的过程实际就是压栈的过程,除了第一个是记录的位置,后面都是记的
n x
如1 x 、2 x、3 x
- 回归的过程就是弹栈的过程
- 递归函数一般有一个递归出口,比如 n = 1 的时候,不再调用自己
f(100000)
就要压10000
次- Chrome的压栈次数范围大概在 11000~12000 左右(可以用阶加算出来,n求和)
- 超过了压栈次数,浏览器就会崩溃
1.3 常用浏览器压栈最大值
- Chrome 12578
- Firfox 26773
- Node 12536
通过函数可以算出来
function computeMaxCallStackSize() {
try {
return 1 + computeMaxCallStackSize();
} catch (e) {
// 报错说明 stack overflow 了
return 1;
}
}
函数提升
1.什么是函数提升
- 如果函数声明是:
function fn(){}
- 不管你把具名函数声明在哪里,它都会跑到第一行
一般是先声明在使用,但函数的声明会默认到第一行,因此可以先写调用后面跟声明
2.同时有一个变量和一个函数 - 函数声明会默认跑到第一行
let
不允许在已经有一个函数的情况下再声明add
(let
的特性是如果变量已经存在则不允许再声明)- 不准用
var
,因为var
声明变量和函数声明在一起的情况比较复杂,懒得去分析
3.什么不是函数提升 let fn = function (){}
- 这是赋值,右边的匿名函数声明不会提升