javascript递归

递归函数就是会直接或者间接调用自身的一种函数。递归是一种强大的编程技术,它把一个问题分解为一组相似的子问题,调用自身去解决它的子问题。

一、汉诺塔

问题描述:有3根柱子和一套直径各不相同的空心圆盘。开始时源柱子上的所有圆盘都按照从小到大的顺序堆叠。目标是通过每次移动一个圆盘到另一根柱子,最终把一堆圆盘移动到目标柱子上,过程中不允许把较大的圆盘放置在较小的圆盘上。

var hanoi=function(disc,src,aux,dst){
    if(disc>0){
        hanoi(disc-1,src,dst,aux);
        console.log("move "+disc+" from "+src+" to "+dst);
        hanoi(disc-1,aux,src,dst);
    }
}

圆盘数量为3的时候解法为:

hanoi函数把一堆圆盘从一根柱子移动到另一根柱子,必要时使用辅助的柱子。

它把问题分解成3个子问题:
首先,移动一对圆盘中较小的圆盘到辅助柱子上,从而露出下面较大的圆盘。

然后,移动下面较大的圆盘到目标柱子上。

最后,它将刚才较小的圆盘从辅助的柱子上再移动到目标柱子上。

传递给hanoi函数的参数包括当前移动的圆盘编号和它将用到的3根柱子。当它调用自身的时候,它去处理当前正在处理的圆盘之上的圆盘。最终,它会以一个不存在的圆盘编号去调用。此时,不执行任何操作。由于该函数对非法值不予理会,就不用担心它会导致死循环。

二、DOM遍历

递归函数可以非常高效地操作树形结构,比如浏览器文档对象模型DOM。每次递归调用处理指定树的一小段。

html结构如下:

<body>
<div class="test">测试div</div>
<span class="test">测试span</span>
<div class="test1">test1 div</div>
</body>

js如下:

<script>
/*定义walk_the_DOM函数,它从某个指定的节点开始,按HTML源码中的顺序访问该树的每个节点。
它会调用一个函数,并依次传递每个节点给它。walk_the_DOM调用自身去处理每一个子节点。
*/
var walk_the_DOM=function walk(node,func){
    func(node);
    node=node.firstChild;
    while(node){
        walk(node,func);
        node=node.nextSibling;
    }
}

/*定义getElementsByAttribute函数。它以一个属性名称字符串和一个可选的匹配值作为参数。
它调用walk_the_DOM,传递一个用来查找节点属性名的函数作为参数。
匹配的节点会累加到一个结果数组中。
*/
var getElementsByAttribute=function(att,value){
    var results=[];

    walk_the_DOM(document.body,function(node){
        var actual=node.nodeType===1&&node.getAttribute(att);
        if(typeof actual==='string' &&( actual===value|| typeof value!=='string')){
            results.push(node);
        }
    });
    return results;
}
console.log(getElementsByAttribute("class","test"));//[div.test, span.test]

三、命名函数表达式和递归

1、递归问题

求阶乘的函数:

function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num*factorial(num-1);
    }
}

正常情况运行没问题,但是下面操作会让它出错:

var anotherFactorial=factorial;//把函数保存在遍历anotherFactorial中
factorial=null;//factorial置为null,此时指向原始函数的引用只剩一个
anotherFactorial(3)

factorial已经不再是函数,所以会报错。

2、arguments.callee实现递归

arguments.callee是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用

//console
function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num*arguments.callee(num-1);
    }
}
var anotherFactorial=factorial;
factorial=null;
anotherFactorial(3) //6

用arguments.callee代替函数名,可以确保无论怎样调用函数都不会出问题。因此,在编写递归函数时,使用arguments.callee总比使用函数名更保险。

问题:在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会报错。

更多严格模式相关内容可参考:javascript 语句和严格模式(三)

3、命名函数表达式实现递归

 创建一个名为f()的命名函数表达式,然后赋值给factorial,即使把函数赋值给了另一个变量,函数的名字f仍然有效,所以递归调用照样能正常完成。

这种方式在严格模式和非严格模式都可行。

//console
var factorial =function f(num){
'use strict'
    if(num<=1){
        return 1;
    }else{
        return num*f(num-1);
    }
}

factorial(3)
//6
var anotherFactorial=factorial;
factorial=null;
anotherFactorial(3)
//6

命名函数表达式也有它的一些经典bug,见javascript 函数和作用域(函数,this)(六)

 

 

本文作者starof,因知识本身在变化,作者也在不断学习成长,文章内容也不定时更新,为避免误导读者,方便追根溯源,请诸位转载注明出处:http://www.cnblogs.com/starof/p/6510723.html有问题欢迎与我讨论,共同进步。

posted @ 2017-03-06 17:56  starof  阅读(2223)  评论(0编辑  收藏  举报