※递归理解:
递归:函数自己调用自己。
递归若有结束的条件,则会一直调用自己,直到满足调用结束条件后,再一层一层向上返回。
若没有结束条件,则会一直调用下去,即无限递归。
- 循环和递归的区别
循环:是有去无回,整个循环部分一直执行;
递归:是有去有回,自己调用自己后,递归语句后面的内容也会继续执行,还会一层层向上返回执行。
看到别人的blog的例子,或许能好理解些:
举个栗子,你用你手中的钥匙打开一扇门,结果去发现前方还有一扇门,紧接着你又用钥匙打开了这扇门,然后你又看到一扇们…但是当你开到某扇门时,发现前方是一堵墙无路可走了,你选择原路返回——这就是递归;
但是如果你打开一扇门后,同样发现前方也有一扇们,紧接着你又打开下一扇门…到了尽头但不会再原路返回就停在那——这就是循环。
看个代码例子,我觉得更容易理解递归的特点:
<div class="block">
<button onclick="baoliyu(3)">抱鲤鱼</button>
<div id="liyuResult"></div>
</div>
<script>
function baoliyu(depth,type){
type = type?type:'liyuResult';
console.log('抱着');
document.getElementById(type).innerHTML += '\n抱着';
if(!depth){
console.log('我的小鲤鱼');
document.getElementById(type).innerHTML += '\n我的小鲤鱼';
}else{
baoliyu(--depth,type);
}
console.log('啦='+depth);
document.getElementById(type).innerHTML += '\n啦;depth='+depth;
}
</script>
执行结果:
后面的啦啦啦,也就是递归后,一层一层向上返回的表象。
第一个depth=0 是进入if后,没有再递归,顺序执行下去的。
第二个depth=0 及后面的depth=1和2,都是递归回归的执行。
※Js递归调用方式:
1.通过函数名递归调用
最简单的方式就是直接通过函数名调用即可,其实适合大部分简单应用场景。
例子如上:
// 调用递归函数begin
baoliyu(2)
// 调用递归函数end
缺点:
函数名是指向函数对象的指针,如果把函数的名与函数对象本身的指向关系断开,此方式将无法找到正确的指向。使用时会很脆弱。
例:
如下代码,将递归函数重新赋值给一个新的变量后,再调用。
就会出现调用自己时找不到原函数名的问题。
<div class="block">
<button onclick="baoliyuNew(2)">抱鲤鱼New</button>
</div>
<script>
//切断函数名关联test
var baoliyuNew = baoliyu;
baoliyu = null;
</script>
执行结果:
2.通过arguments.callee调用
例:
<div class="block">
<button onclick="baoliyuCallee(2)">抱鲤鱼callee</button>
<div id="liyuResultCallee"></div>
</div>
<div class="block">
<button onclick="baoliyuNew(2)">抱鲤鱼New</button>
</div>
<script>
function baoliyuCallee(depth){
console.log('抱着');
document.getElementById('liyuResultCallee').innerHTML += '\n抱着';
if(!depth){
console.log('我的小鲤鱼');
document.getElementById('liyuResultCallee').innerHTML += '\n我的小鲤鱼';
}else{
// 调用递归函数begin
arguments.callee(--depth);
// 调用递归函数end
}
console.log('啦');
document.getElementById('liyuResultCallee').innerHTML += '\n啦';
}
//切断函数名关联test
var baoliyuNew = baoliyuCallee;
baoliyuCallee = null;
</script>
结果:
缺点:
虽没有上面不会被函数名的限制问题,但,
严格模式下是禁止使用arguments.callee
3.通过闭包实现
在第一个例子的基础上,加上一层闭包封装。这个方法是综合简单又好用的方式。
// 调用递归函数begin
function baoliyuMult(dept){
return baoliyu(dept,'liyuResultMult');
}
// 调用递归函数end
//切断函数名关联test
var baoliyuNew = baoliyuMult;
baoliyuMult = null;
结果:
※Js递归 常用的几个例子
- 斐波那契数列
1, 1, 2, 3, 5, 8, 13, 21, 34, … 求第n个数是多少。
function fibonacci(n){
if(n<=0){
return 0;
}
if(n<=2){
return 1;
}
return arguments.callee(n-1) + arguments.callee(n-2);
}
console.log('fibonacci(5)='+fibonacci(6));
- 汉诺塔
有三根相邻的柱子,标号为A,B,C,A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘,要把所有盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方。
function hanoi(n,from,ass,to){
if(n>0){
hanoi(n-1,from,to,ass);
console.log("移动第"+n+"个从"+from+"到"+to);
hanoi(n-1,ass,from,to);
}
}
hanoi(3,"A","B","C");
- 最大公约数
指两个或多个整数共有约数中最大的一个。
// 最大公约数【辗转相除】
function getGCD(a,b){
var c = a%b;
if(c!==0){
return arguments.callee(b,c);
}else{
return b;
}
}
console.log('getGCD='+getGCD(319,377));
附:
所有的demo:
https://github.com/SummerOrange/JSDemos/blob/master/recursive.html
※【实际应用】组装无限嵌套循环的值
待组装的无限循环的JSON格式如下:
就是第一层数据嵌套一个对象数组comment,第二层嵌套第三层对象数组comment。。。以此类推,无限循环。总层级数未知。
目标需求:
希望将第三层及以上的comment的数据都追加到第二层comment中。(聊天列表展示)
源JSON数据:
{
"comment":[
{
"id":"1.1",
"comment":[
{
"id":"1.1.1",
"comment":[
{
"id":"1.1.1.1",
"comment":[
{
"id":"1.1.1.1.1",
"comment":[
]
}
]
},
{
"id":"1.1.1.2",
"comment":[
]
}
]
},
{
"id":"1.1.2",
"comment":[
]
}
]
},
{
"id":"1.2",
"comment":[
]
}
]
}
目标JSON数据:
{
"comment":[
{
"id":"1.1",
"comment":[
{
"id":"1.1.1",
"comment":[
]
},
{
"id":"1.1.2",
"comment":[
]
},
{
"id":"1.1.1.2",
"comment":[
]
},
{
"id":"1.1.1.1",
"comment":[
]
},
{
"id":"1.1.1.1.1",
"comment":[
]
}
]
},
{
"id":"1.2",
"comment":[
]
}
]
}
解析方法:
// 调用
let list = data //总数据源
// 循环,为了将第二层单独剥离
for (let i = 0; i < list.length; i++) {
let secondList = [] //定义一个新数据,存放第三、第四。。的comment数据
this.dealCommentSecond(list[i], secondList) //递归调用
list[i].comment = secondList //将组装好的数据赋值给第二层的comment
}
//递归方法
dealCommentSecond (data, secondList) {
let rec = data.comment
for (let i = 0; i < rec.length; i++) {
if (rec[i] && rec[i].comment && rec[i].comment.length > 0) {
secondList.push(rec[i])
this.dealCommentSecond(rec[i], secondList)
} else {
secondList.push(rec[i])
}
}
}