什么是堆栈溢出
堆栈(Stack)是一种抽象数据结构,是一组相同数据类型的组合,所有的操作均在堆栈顶端进行,具有“后进先出”的特性,即最后一个放入堆栈中的物体总是被最先拿出来。堆栈中两个最重要的是PUSH(进栈)和POP(出栈), PUSH操作在堆栈的顶部加入一 个元素,POP操作相反, 在堆栈顶部移去一个元素, 并将堆栈的大小减一。水满则溢,堆栈是有一
定容量限制的,当超出了该容量限制,就会发生溢出。(引用百度百科)
解决:
1、使用setTimeout(推荐)
function callback(f) {
f();
}
function Foo() {
// Foo(); //执行1000次左右会发生堆栈溢出的错误,
setTimeout(Foo, 0); //永远不会堆栈溢出
}
Foo();
2、闭包解决
闭包:
有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式就是,在一个函数中创建另一个函数。
闭包的作用:
访问函数内部变量、保持函数在环境中一直存在,不会被垃圾回收机制处理
因为函数内部声明 的变量是局部的,只能在函数内部访问到,但是函数外部的变量是对函数内部可见的,这就是作用域链的特点了。
子级可以向父级查找变量,逐级查找,找到为止
因此我们可以在函数内部再创建一个函数,这样对内部的函数来说,外层函数的变量都是可
3见的,然后我们就可以访问到他的变量了。
function isEven(num){
function isEvenInner(num){
if(num === 0){return true;}
if(num === 1){return false;}
return function(){
return isEvenInner(Math.abs(num)-2);
}
}
function simplify(func,num){
var value=func(num);
while(typeof value == 'function'){
value=value();
}
return value;
}
return simplify.bind(null,isEvenInner)(num)
}
console.log(isEven(100000)); //num太大会导致浏览器卡顿
3、尾调用
尾调用(Tail Call)是函数式编程的一个重要的概念,简单的来说就是:某个函数的最后一步是调用另一个函数。
例如下面的例子就是尾调用:
function f(x) {
return g(x)
}
那么,我们来总结一下,尾调用的特点:
首先,尾调用必须满足:函数的最后一步是return另一个函数(如上述例子),这里和闭包有点像;
其次,return 后面的表达式必须仅仅是某个函数的调用,除此之外不能包含其它任何别的操作;
以下不属于尾调用:
function f(x) {
var a = 3
return a + g(x) // 表达式除了函数调用还包含了加法操作,所以这不是尾调用
}
function f(x) {
g(x) // 没有return
}
function f(x) {
g(x)
return undefined //返回的是undefined
}
function f(x) {
let a = g(x)
return a // 返回的是函数的结果
}
总之,如果问如何解决堆栈溢出,可以:
- 把递归算法改为非递归,比如在堆区中申请一个栈容器,然后用这个栈模拟函数的递归调用
- 把递归改成尾调用
- 把递归函数中的变量改成静态变量,或者尽量不用局部变量,而是在堆中创建变量,减少每次函数调用栈的大小
- 存在相关函数可以在创建线程时指定栈空间大小