js递归遍历讲解

JavaScript的递归遍历会经常遇到,适当的运用递归遍历,可以提高代码性质量。

1.某些时候递归能替换for循环

我们先看一下下面2个例子。

var arrList = [1,2,3,5,100,500,10000,10000,1000,10000002]
 //for循环测试
 function forTest(){
     console.time("for循环")
     for(let i in arrList){
         console.log(((arrList[i] + arrList[i]) * 5 - arrList[i])/arrList[i])
     }
     console.timeEnd("for循环")
 }
 //递归遍历测试
 function recursive() {
     console.time("递归遍历")
     const testFun = function (i) {
         console.log(((arrList[i] + arrList[i]) * 5 - arrList[i])/arrList[i])
         if(i == arrList.length - 1){
             return
         }
         i++
         testFun(i)
     }
     testFun(0)
     console.timeEnd("递归遍历")
 }
 forTest()
 recursive()

运行结果:


在这里插入图片描述

可以看到,for循环去遍历一个数组和用递归遍历去遍历同一个数组得到的结果一样,耗时也几乎相同。但是写法上有很大区别。

递归特点

每个递归都有一个结束递归的条件,上例中的:if(i == arrList.length - 1){ return }。每一个递归都会在函数内部把函数本身调用一次,但是函数在每次运行的时候,都会发生一些变化,用来触发递归的结束,上例中,testFun函数在内部调用了自己,并且每次调用i的值会+1,最终触发了结束条件,让递归结束。

2.使用递归,可以轻松实现多级遍历

我们先看下面的代码:

var data = [
 {
     name: "所有物品",
     children: [
         {
             name: "水果",
             children: [{name: "苹果", children: [{name: '青苹果'}, {name: '红苹果'}]}]
         },
         {
             name: '主食',
             children: [
                 {name: "米饭", children: [{name: '北方米饭'}, {name: '南方米饭'}]}
             ]
         },
         {
             name: '生活用品',
             children: [
                 {name: "电脑类", children: [{name: '联想电脑'}, {name: '苹果电脑'}]},
                 {name: "工具类", children: [{name: "锄头"}, {name: "锤子"}]},
                 {name: "生活用品", children: [{name: "洗发水"}, {name: "沐浴露"}]}
             ]
         }
  ]
}]

某些时候,坑逼后台让我们遍历上面的一个数组,最后在页面上显示没一级的最后一个数据。就是下面的数据:

青苹果;红苹果;北方米饭;南方米饭;联想电脑;苹果电脑;锄头;锤子;洗发水;沐浴露;

我们先用遍历的方式来实现:

//普通遍历实现
var forFunction = function () {
    var str = ""
    data.forEach(function(row){
        row.children.forEach(function(row){
            row.children.forEach(function(row){
                row.children.forEach(function(row){
                    str += (row.name + ";")
                })
            })
        })
    })
    console.log(str)
}
forFunction()
//输出:青苹果;红苹果;北方米饭;南方米饭;联想电脑;苹果电脑;锄头;锤子;洗发水;沐浴露;

可以看到,前端累死累死写了4个forEach实现了产品要的效果,这时候如果再加点别的什么逻辑,就很难弄了。这时候我们尝试用递归的思想实现:

//递归遍历实现
var recursiveFunction = function(){
    var str = ''
    const getStr = function(list){
        list.forEach(function(row){
            if(row.children){
                getStr(row.children)
            }else {
                str += row.name + ";"
            }
        })
    }
    getStr(data)
    console.log(str)
}
recursiveFunction()
//输出:青苹果;红苹果;北方米饭;南方米饭;联想电脑;苹果电脑;锄头;锤子;洗发水;沐浴露;

可以看到,递归的方式来实现的时候,我们只需要一个for循环,每次遍历接受到的数据,通过判断是否还有children,没有就代表是最后一级了,有就继续把children这个list传给函数继续遍历,最后就得到了我们想要的数据。

很明显,forEach的遍历的方式能实现多级的遍历,并且数据格式可以灵活一些,但是遍历的层级有限,而且对于未知层级的情况下,就无从下手了。
递归遍历,理论上,只要内存够用,你能实现任意层级的遍历,但缺点也很明显,没一个层级里面需要有固定的数据格式,否则无法遍历。

3.递归遍历轻松实现多个异步结果的依次输出

我们先看一下下面的需求,需要依次输出1,2,3,4,5,每个输出中间间隔1s
我们先尝试下面的方式:

//常规实现
var forTest = function () {
   console.time("for时间")
    for (let i = 0; i < 5; i++) {
        setTimeout(function () {
            console.log('for循环输出:' + (i + 1))
            if(i+ 1 === 5){
                console.timeEnd("for时间")
            }
        }, 1000 * (i + 1))
    }
}
forTest()
//每隔一秒输出了1,2,3,4,5

上面我们用let当前作用域的特点实现了每隔1s输出,其实可以看出,是一起执行的,但是由于间隔时间不一样,导致结果是每隔一秒输出的。
我们再用递归的方式实现:

//递归遍历实现
var recursiveTest = function(){
   console.time("递归时间")
    const test = function (i) {
        setTimeout(function () {
            i = i + 1
            console.log('递归输出:' + i)
            if(i < 5){
                test(i)
            }else {
                console.timeEnd("递归时间")
            }
        }, 1000)
    }
    test(0)
}
recursiveTest()
//每隔一秒输出1,2,3,4,5

这里利用递归实现就很好理解了,在setTimeout里面输出了之后,再开始下一个1s的输出。
最后我们看一下运行截图:

输出结果

通过截图上的耗时来看:递归遍历需要的时间更长,但是更好理解一下,而且不依赖es6的环境。

4.递归遍历实现排序

var quickSort = function(arr) {
if (arr.length <= 1) {//如果数组长度小于等于1无需判断直接返回即可
    return arr;
}
var pivotIndex = Math.floor(arr.length / 2);//取基准点
var pivot = arr.splice(pivotIndex, 1)[0];//取基准点的值,splice(index,1)函数可以返回数组中被删除的那个数
var left = [];//存放比基准点小的数组
var right = [];//存放比基准点大的数组
for (var i = 0; i < arr.length; i++){ //遍历数组,进行判断分配
    if (arr[i] < pivot) {
        left.push(arr[i]);//比基准点小的放在左边数组
    } else {
        right.push(arr[i]);//比基准点大的放在右边数组
    }
}
//递归执行以上操作,对左右两个数组进行操作,直到数组长度为<=1;
return quickSort(left).concat([pivot], quickSort(right));
};

var arr = [14, 50, 80, 7, 2, 2, 11];
console.log(quickSort(arr));

这是利用递归实现的快速排序,效率很高,有兴趣的同学可以试试普通的遍历实现,再进行比较。

5.总结

1.很多时候可以用递归代替循环,可以理解为递归是一种特殊的循环,但通常情况下不推荐这样做。
2.递归一般是在函数里面把函数自己给调用一遍,通过每次调用改变条件,来结束循环。
3.递归在数据格式一致,在数据层级未知的情况下,比普通的遍历更有优势。
4.递归在异步的时候,更容易理解,且更容易实现,因为可以在异步的回调里面,调用自己来实现每次都能拿到异步的结果再进行其他操作。
5.递归实现的快速排序比普通遍历实现的排序效率更好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容

  • 前言 React Fiber 不是一个新的东西,但在前端领域是第一次广为认知的应用。几年前全新的Fiber架构让刚...
    这个前端不太冷阅读 5,509评论 2 8
  • 第一章: 函数式编程主要基于数学函数和它的思想。 1.1 函数与js方法: 函数是一段可以通过其名称被调用的代码,...
    yuhuan121阅读 11,904评论 5 8
  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,046评论 0 29
  • 33、JS中的本地存储 把一些信息存储在当前浏览器指定域下的某一个地方(存储到物理硬盘中)1、不能跨浏览器传输:在...
    萌妹撒阅读 2,079评论 0 2
  • 黑色的海岛上悬着一轮又大又圆的明月,毫不嫌弃地把温柔的月色照在这寸草不生的小岛上。一个少年白衣白发,悠闲自如地倚坐...
    小水Vivian阅读 3,102评论 1 5