递归那点事(下)

上篇我们说道了一些用到递归的一些面试题,也讲了递归使用命名式函数的完善。

不过,面试题终究是面试题,而递归在实际开发中,需要慎用。(本人不多的项目中,用到递归次数不多,仅几次是用于处理数据,该数据有类似父结构的子结构,用递归很方便)

下篇,我们来讲讲递归的优缺点,再引申一下事件循环机制。

 

递归优点:

1.代码简洁。

2.方便理解。

缺点:

1、时间和空间的消耗比较大。

2、重复计算。

3、调用栈溢出。

 

这里提一下事件循环机制,以帮助理解递归。

js是单线程的,这个是由其用途决定的,这是js的核心,不会改变。

但是单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

所以为了完善这一点,就有了同步任务和异步任务。

下图很好的诠释了事件循环机制,大致过程为:js从上向下读代码,主线程形成堆和栈,栈中代码执行时会调用各种外部API,将各类事件加入任务队列。在栈中的同步任务执行完后,就会从任务队列中取出这些异步任务执行。

所以我们在使用递归时,函数不断调用,栈中任务不断增加,调用的函数因没有执行完仍在栈中没有释放,只有我们满足条件不再递归调用函数时,函数从栈顶向栈底一个个执行完毕释放掉。

每次函数调用都要在栈中分配空间,每次函数调用向栈压入也需要一定时间,可见递归是对时间和空间的消耗比较大的。

还有栈的容量是有限的,若是我们递归层次太多,函数执行嵌套过深,还没有能等到函数执行完毕释放,栈就满了,就会形成栈溢出。

 

这里有一道面试题值得看看

//以下代码迭代太深会造成stack溢出, 改写成不会出错的代码
var queue = ...... //这是一个很大很大的数组
var nextItem = function(){
    var item = queue.pop();
    if(item){
        nextItem();
    }
}

若是理解了事件循环机制,我们就能理解以下这个改错方案了

var nextItem = function(){
    var item = queue.pop();
    if(item) {
        setTimeout(function(){
        nextItem()
    },0)
    }
}

即,使用了延时定时器,将本在同步任务的函数调用,变到了异步任务——

1.在第一层nextItem函数执行时,item若存在,开启定时器,第二层nextItem函数的调用放到了任务队列中,并不执行。

2.然后第一层nextItem函数执行完毕,在栈中释放。

3.栈空后,取任务队列中的第二层nextItem函数执行。

如此循环,像是递归,但是却非递归,我们会确保栈空后才去执行任务队列函数,这样就不会造成栈溢出了。

 

参考:

阮一峰 再谈Event Loop http://www.ruanyifeng.com/blog/2014/10/event-loop.html

递归优缺点 https://blog.csdn.net/haovin/article/details/97033783

posted @ 2020-08-29 21:50  桔梗❤  阅读(126)  评论(0编辑  收藏  举报