用 JavaScript 的方式理解递归
原文地址
1. 递归是啥?
递归概念很简单,“自己调用自己”(下面以函数为例)。
在分析递归之前,需要了解下 JavaScript 中“压栈”(call stack
) 概念。
2. 压栈与出栈
栈是什么?可以理解是在内存中某一块区域,这个区域比喻成一个箱子,你往箱子里放些东西,这动作就是压栈。所以最先放下去的东西在箱子最底下,最后放下去的在箱子最上面。把东西从箱子中拿出来可以理解为出栈。
所以得出结论,我们有个习惯,拿东西是从上面往下拿,最先放下去的东西在箱子的最底下,最后才能拿到。
在 JavaScript 中,调用函数时,都会发生压栈行为,遇到含 return
关键字的句子或执行结束后,才会发生出栈(pop)。
来看个例子,这个段代码执行顺序是怎样的?
function fn1() {return 'this is fn1'
}function fn2() {fn3()return 'this is fn2'
}function fn3() {let arr = ['apple', 'banana', 'orange']return arr.length
}function fn() {fn1()fn2()console.log('All fn are done')
}fn()
复制代码
上面发生了一系列压栈出栈的行为:
- 首先,一开始栈(箱子)是空的
- 函数
fn
执行,fn
先压栈,放在栈(箱子)最底下 - 在函数 fn 内部,先执行函数
fn1
,fn1
压栈在fn
上面 - 函数
fn1
执行,遇到return
关键字,返回this is fn1
,fn1
出栈,箱子里现在只剩下fn
- 回到
fn
内部,fn1
执行完后,函数fn2
开始执行,fn2
压栈,但是在fn2
内部,遇到fn3
,开始执行fn3
,所以fn3
压栈 - 此时栈中从下往上顺序为:
fn3
<=fn2
<=fn
,fn
在最底下 - 以此类推,函数
fn3
内部遇到return
关键字的句子,fn3
执行完结束后出栈,回到函数fn2
中 ,fn2
也是遇到return
关键字,继续出栈。 - 现在栈中只有
fn
了,回到函数fn
中,执行console.log('All fn are done'
) 语句后,fn
出栈。 - 现在栈中又为空了。
上面步骤容易把人绕晕,下面是流程图:
再看下在 chrome 浏览器环境下执行:
3. 递归
先看下简单的 JavaScript 递归
function sumRange(num) {if (num === 1) return 1;return num + sumRange(num - 1)
}sumRange(3) // 6
复制代码
上面代码执行顺序:
- 函数
sumRange(3)
执行,sumRange(3)
压栈,遇到return
关键字,但这里还马上不能出栈,因为调用了sumRange(num - 1)
即sumRange(2)
- 所以,
sumRange(2)
压栈,以此类推,sumRange(1)
压栈, - 最后,
sumRange(1)
出栈返回 1,sumRange(2)
出栈,返回 2,sumRange(3)
出栈 - 所以
3 + 2 + 1
结果为 6
看流程图
所以,递归也是个压栈出栈的过程。
递归可以用非递归表示,下面是上面递归例子等价执行
// for 循环
function multiple(num) {let total = 1;for (let i = num; i > 1; i--) {total *= i}return total
}
multiple(3)
复制代码
4. 递归注意点
如果上面例子修改一下,如下:
function multiple(num) {if (num === 1) console.log(1)return num * multiple(num - 1)
}multiple(3) // Error: Maximum call stack size exceeded
复制代码
上面代码第一行没有 return
关键字句子,因为递归没有终止条件,所以会一直压栈,造成内存泄漏。
递归容易出错点
- 没有设置终止点
- 除了递归,其他部分的代码
- 什么时候递归合适
好了,以上就是 JavaScript 方式递归的概念。
下面是练习题。
6. 练习题目
- 写一个函数,接受一串字符串,返回一个字符串,这个字符串是将原来字符串倒过来。
- 写一个函数,接受一串字符串,同时从前后开始拿一位字符对比,如果两个相等,返回
true
,如果不等,返回false
。 - 编写一个函数,接受一个数组,返回扁平化新数组。
- 编写一个函数,接受一个对象,这个对象值是偶数,则让它们相加,返回这个总值。
- 编写一个函数,接受一个对象,返回一个数组,这个数组包括对象里所有的值是字符串
7. 参考答案
参考1
function reverse(str) {if(str.length <= 1) return str; return reverse(str.slice(1)) + str[0];
}reverse('abc')
复制代码
参考2
function isPalindrome(str){ if(str.length === 1) return true;if(str.length === 2) return str[0] === str[1]; if(str[0] === str.slice(-1)) return isPalindrome(str.slice(1,-1)) return false;
}var str = 'abba'
isPalindrome(str)
复制代码
参考3
function flatten (oldArr) {var newArr = [] for(var i = 0; i < oldArr.length; i++){ if(Array.isArray(oldArr[i])){ newArr = newArr.concat(flatten(oldArr[i])) } else { newArr.push(oldArr[i])}}return newArr;
}flatten([1,[2,[3,4]],5])
复制代码
参考4
function nestedEvenSum(obj, sum=0) {for (var key in obj) { if (typeof obj[key] === 'object'){ sum += nestedEvenSum(obj[key]); } else if (typeof obj[key] === 'number' && obj[key] % 2 === 0){ sum += obj[key]; }} return sum;
}nestedEvenSum({c: 4,d: {a: 2, b:3}})
复制代码
参考5
function collectStrings(obj) {let newArr = []for (let key in obj) {if (typeof obj[key] === 'string') {newArr.push(obj[key])} else if(typeof obj[key] === 'object' && !Array.isArray(obj[key])) {newArr = newArr.concat(collectStrings(obj[key]))}}return newArr
}var obj = {a: '1',b: {c: 2,d: 'dd'}}collectStrings(obj)
复制代码
5. 总结
递归精髓是,往往要先想好常规部分是怎样的,在考虑递归部分,下面是 JavaScript 递归常用到的方法
- 数组:
slice
,concat
- 字符串:
slice
,substr
,substring
- 对象:
Object.assign
用 JavaScript 的方式理解递归相关推荐
- 【javascript】深入理解对象
为什么80%的码农都做不了架构师?>>> 今天学习的主题是 JavaScript对象. 要创建一个JavaScript对象大家应该都觉得很简单,直接写上一行 var obj = ...
- 我对javascript对象的理解
前言 JavaScript这门语言除了基本类型都是对象,可以说JavaScript核心就是对象,因此理解JavaScript对象及其种种特性至关重要,这是内功.本文介绍了我对es5对象,原型, 原型链 ...
- python语言的理解-使用Python语言理解递归
递归 一个函数在执行过程中一次或多次调用其本身便是递归,就像是俄罗斯套娃一样,一个娃娃里包含另一个娃娃. 递归其实是程序设计语言学习过程中很快就会接触到的东西,但有关递归的理解可能还会有一些遗漏,下面 ...
- python递归详解_Python理解递归的方法总结
递归 一个函数在执行过程中一次或多次调用其本身便是递归,就像是俄罗斯套娃一样,一个娃娃里包含另一个娃娃. 递归其实是程序设计语言学习过程中很快就会接触到的东西,但有关递归的理解可能还会有一些遗漏,下面 ...
- JavaScript面向对象——深入理解寄生组合继承
JavaScript面向对象--深入理解寄生组合继承 之前谈到过组合继承,会有初始化两次实例方法/属性的缺点,接下来我们谈谈为了避免这种缺点的寄生组合继承 寄生组合继承: 思路:组合继承中,构造函数继 ...
- JavaScript面向对象——深入理解原型继承
JavaScript继承--深入理解原型继承 原型继承 // 父类function School (name, address) {this.name = namethis.address = add ...
- 递归循环一个无限极数组_理解递归、尾调用优化和蹦床函数优化
想要理解递归,您必须先理解递归.开个玩笑罢了, 递归 是一种编程技巧,它可以让函数在不使用 for 或 while 的情况下,使用一个调用自身的函数来实现循环. 例子 1:整数总和 例如,假设我们想要 ...
- JavaScript的重载和递归
下面是JavaScript的重载和递归,希望可以帮助到有需要的小伙伴 JavaScript没有重载 在其他开发语言中,函数具有一种特性,叫做重载.所谓重载,就是定义多个同名的函数,但每一个函数接收的参 ...
- (JAVA编成练习):递归的使用,简单的列子帮你理解递归。
目录 前言: 1.什么是递归? 2.递归的定义: 3.递归图示: 4.第一题: (1)题目: (2)代码: a.关键处解析: b.完整代码: 5.第二题: (1)题目: (2)代码: a.关键处解析: ...
最新文章
- mysql中添加下拉,如何从MySQL填充的下拉列表中发布数据
- SmartImageView框架的使用
- java语言和www技术 阶段性测试一_2018春季【贵州电大】[JAVA语言与WWW技术(省)]04任务阶段性测验(答案)...
- Docker安装及配置
- python for loop步进值_python-对for循环的结果进行排序时保持值连...
- mysql chroot_在chroot环境下将MySQL日志输出到syslog
- 李智:用数学来理解世界
- 曾小伟:谁没被“不可思议”的薪资吸引过?
- 人工神工机器人是什么_为什么企业要用电销机器人代理人工
- 青岛大学计算机科学技术学院官网,田呈亮 - 青岛大学 - 计算机科学技术学院
- JDK 1.8新特性
- 在企业级管理软件领域,国内尚没有“好”产品
- 计算机网络自顶向下方法
- RT-Thread与cubemx|74HC595驱动数码管详解
- 3.3.3 使用集线器的星形拓补
- 逻辑思维训练题:切西瓜之一共切10刀,最多能将西瓜切成多少块?
- 城镇污水处理厂工艺概述及提标改造路线
- 常用的Python3关键词提取方法
- helm 构建 chart
- latex中lstlisting使用
热门文章
- MTFBWU的完整形式是什么?
- Python datetime isocalendar()方法与示例
- 为什么要返回softmax_为什么softmax搭配cross entropy是解决分类问题的通用方案?
- python处理中文字符串_处理python字符串中的中文字符
- java三路快排,java二路快排很慢
- 349. 两个数组的交集 golang
- go语言扫描四位数可用域名
- C语言 有符号字符型输出 面试题
- 关于C++子类父类成员函数的覆盖和隐藏
- python字符串是用双引号括起来的_用python连接字符串列表并用引号将每个字符串括起来...