整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

javaScript-递归函数

javaScript-递归函数

归函数是一种特殊的函数,递归函数允许在函数定义中调用函数本身。考虑对于如下计算:n!=n*(n-1)*(n-2)*...*1

希望能写一个简单的函数完成对n!的求值。观察上面等式发现:(n-1)!=(n-1)*(n-2)*...*1

则有如下等式:n!=n*(n-1)!

注意到等式左边需要求n的阶乘,而等式右边则是求n-1的阶乘。实质都是一个函数,因此可将求阶乘的函数定义:

<script type="text/javascript">
 //定义求阶乘的函数
 var factorial=function(n)
 {
 //只有n的类型是数值,才执行函数
 if(typeof(n)=="number")
 {
 //当n等于1时,直接返回1
 if(n==1)
 {
 return 1;
 }
 //当n不等于1时,通过递归返回值
 else
 {
 return n*factorial(n-1);
 }
 }
 //当参数不是数值时,直接返回
 else
 {
 alert("参数类型不对!");
 }
 }
 //调用阶乘函数
 alert(factorial(5));
 </script>

结果:


上面程序中粗体字代码再次调用了factorial()函数,这就是在函数里调用函数本身,也就是所谓的递归。上面程序执行的结果是120,可以正常求出5的阶乘。注意到程序中判断参数时,先判断参数n是否为数值,而且要求n大于0才会继续运算。事实上,这个函数不仅要求n为数值,而且必须是大于0的整数,否则函数不仅不能得到正确结果,而且将产生内存溢出。

对于上面递归函数,当n为一个大于0的整数,例如5时,5的阶乘为4的阶乘和5的乘积;同理,4的阶乘为3的阶乘和4的乘积……依次类推,直到最后1的阶乘,代码中已经写明:当n=1时,返回值为1。然后反算回去,所有的值都变成已知的。反过来,当n为负数,例如-1时,-1的阶乘为-1与-2的阶乘的乘积,-2的阶乘为-2和-3的阶乘的乘积……这将一直追溯到负无穷大,没有尽头,导致程序溢出。

可见,递归的方向很重要,一定要向已知的方向递归。对于上例而言,因为1的阶乘是已知的,因此递归一定要追溯到1的阶乘,递归一定要给定中止条件,这一点与循环类型。没有中止条件的循环是死循环,不向中止点追溯的递归是无穷递归。



注意:递归一定要向已知点追溯,这样才能保证递归有结束的时候

 好程序员web前端培训分享JavaScript学习笔记之递归函数,什么是递归函数在编程世界里面,递归就是一个自己调用自己的手段,递归函数: 一个函数内部,调用了自己,循环往复


// 下面这个代码就是一个最简单的递归函数// 在函数内部调用了自己,函数一执行,就调用自己一次,在调用再执行,循环往复,没有止尽function fn() {

fn()}fn()

· 其实递归函数和循环很类似

· 需要有初始化,自增,执行代码,条件判断的,不然就是一个没有尽头的递归函数,我们叫做 死递归

简单实现一个递归

· 我们先在用递归函数简单实现一个效果

· 需求: 求 1 至 5 的和

· 先算 1 + 2 得 3

· 再算 3 + 3 得 6

· 再算 6 + 4 得 10

· 再算 10 + 5 得 15

· 结束

· 开始书写,写递归函数先要写结束条件(为了避免出现 “死递归”)

function add(n) {

// 传递进来的是 1 // 当 n===5 的时候要结束 if (n === 5) {

return 5

}}

add(1)

· 再写不满足条件的时候我们的递归处理

function add(n) {

// 传递进来的是 1 // 当 n===5 的时候要结束 if (n === 5) {

return 5

} else {

// 不满足条件的时候,就是当前数字 + 比自己大 1 的数字 return n + add(n + 1)

}}add(1)

预习:提前了解一下对象

· 对象是一个复杂数据类型

· 其实说是复杂,但是没有很复杂,只不过是存储了一些基本数据类型的一个集合

var obj = {

num: 100,

str: 'hello world',

boo: true}

· 这里的 {} 和函数中的 {} 不一样

· 函数里面的是写代码的,而对象里面是写一些数据的

· 对象就是一个键值对的集合

· {} 里面的每一个键都是一个成员

· 也就是说,我们可以把一些数据放在一个对象里面,那么他们就互不干扰了

· 其实就是我们准备一个房子,把我们想要的数据放进去,然后把房子的地址给到变量名,当我们需要某一个数据的时候,就可以根据变量名里面存储的地址找到对应的房子,然后去房子里面找到对应的数据

创建一个对象

· 字面量的方式创建一个对象

// 创建一个空对象var obj = {}

// 像对象中添加成员obj.name = 'Jack'obj.age = 18

· 内置构造函数的方式创建对象

// 创建一个空对象var obj = new Object()

// 向对象中添加成员obj.name = 'Rose'obj.age = 20

· Object 是 js 内置给我们的构造函数,用于创建一个对象使用的

在前面:本文为DataHunter前端技术培训系列的第一篇文章,后续将有更多精彩内容放出,赶紧关注我们的公众号来get实时更新吧!

循环引用陷阱

JavaScript的GC机制是自动进行。当一个对象断开所有的指针引用以后,GC就会回收这部分资源。但循环引用会导致指针无法断开,请看如下代码:

由于a和b两个对象互相引用指针,形成了闭环,就会导致无法触发GC,所占资源也就无法释放。

在JSON.stringify对JavaScript数据结构进行序列化的时候,也会遇到循环引用的陷阱。例如:

JavaScript的数据类型和JSON序列化策略

通过JavaScript的typeof操作符,我们可以得到的数据类型有

string/number/ function/boolean/symbol/object/undefined。

其中object包括Object/Array/RegExp/Date/null等,function包括class类。所有这些类型,从JSON序列化的角度,我们可以分为三类:

由于JSON标准只具备string/number/boolean/null等有限的几种值类型,所以JS对象在序列化后会有变化。下面是几种特殊类型在转换时需要注意的问题:

  • function无法被序列化。

  • Symbol对象无法被序列化。

  • Date对象无法被序列化。

  • ES6的class的typeof是function,无法序列化。

  • number的NaN会被转成null。

  • RegExp默认会被转成空对象。

  • TypedArray并不继承自Array,所以会被序列化成对象而不是数组。

  • undefined由于会被JSON忽略,所以反序列化后对象会有键的变化。看例子:

可以看到,in操作符在obj和copy两个对象中会出现不同的返回结果。所以在对反序列化的数据进行操作时,in操作符要小心使用。

通过JSON的序列化策略我们可以看出,循环引用可能会发生在mixed类型上,所以我们可以在序列化过程的最外层,建立一个mixed指针的数组。当数组已经具备此指针,就可以断定该指针数据已经被序列化,从而中断进一步序列化过程。

首先我们写一个mixed类型判断函数:

通过JSON.stringify的第二个回调处理理函数参数,我们植入这个类型判断,并启用循环引用陷阱规避。

JavaScript对象的分支递归和深拷贝

我们知道,Object.assign会实现一次Object或者Array的浅拷贝。但这种浅拷贝有时并不能满足我们的需求。有时我们还会需要对对象的分支做值更改检测,或者分支合并。这时候都需要对对象的所有分支做一次递归。

JSON.stringify其实就是做了了一次分支递归。对象分支递归的策略和序列化策略基本一致,我们拿对象的深拷贝来举例说明。

我们首先针对上述三种数据类型,做一个类型判断函数。

此外,由于Object和Array都是mixed类型,但不是同一个构造函数,所以我们需要对Object和Array再做一次类型细分,以便构造新的复制对象。

创建一个分支递函数,并规避循环引用陷阱。

可以看到循环引用的部分都变成undefined了。

在ES6下,可以通过Symbol对象定义一个循环引用的标记并返回,在运行时对此类值做自定义的处理,防止对象log出来太难看。