函数表达式---递归和闭包总结

一、定义函数的两种方式:函数声明和函数表达式

1、函数声明:

// 语法:
function funName(arg0,arg1,arg2){
     // 函数体
}

函数声明有个重要特征是:函数声明提升,意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。

sayHi();   // 函数调用
function sayHi(){     // 函数声明
     alert("Hi!");
}

2、函数表达式:

var funName = function(arg0,arg1,arg2){
      // 函数体
}

函数表达是很像常规的变量赋值,创建一个函数将它赋值给funName。这种情况下创建的函数叫做 匿名函数。匿名函数的name值是空字符串。

函数表达是和函数声明不一样,使用函数表达式时候必须先赋值在使用。

二、递归
概念:递归函数是在一个函数通过名字调用自身的情况下构成的,
递归算法的特点:

  • 在函数过程中调用自身。
  • 在递归过程中,必须有一个明确的条件判断递归的结束,既递归出口。
  • 递归算法简洁但效率低,通常不作为推荐算法。
    上面这些是百度百科的解释,讲的也是十分明确,大家配合实例来细细琢磨。

例如:

function funNum(num){       // 阶乘递归
    if(num <= 1){
          return 1
    } else {
          return num * funNum(num - 1) 
   }
}

上面的递归代码看似没有什么问题,但是当你将函数赋值给另一个变量就会出错,如下:

var a= funNum;  // 将函数赋值给a变量
funNum = null;  // 将函数设置成null,现在funNum已经不是一个函数了
alert(a(4)); //出错!   所以会出错

上面代码出错的问题可以使用arguments.callee()来解决,arguments.callee()是一个指向正在执行的函数指针,因此可以用它来实现递归,代码 如下:

function funNum(num){
     if(num <= 0){
          return 1
     } else {
         return num * arguments.callee(num - 1);   // 指向正在执行的函数指针,确保无论怎样调用函数都不会出问题。因此,在编写递归函数时,使用 arguments.callee 总比使用函数名更保险。
     }
}
console.log(funNum(4));       // 24
var a = funNum;
 funNum = null;
 console.log(a(4));    // 24

上面的代码在个严格模式下会报错,不过可以使用命名函数表达是来达成相同的效果:

"use strict"      // 严格模式
var funNum = (function f(num){
    if(num <= 0){
        return 1;
    } else {
        return num * f(num - 1)
    }
})
console.log(funNum(4));
var a = funNum;
funNum = null;
console.log(a(4))

三、闭包
这里参考阮一峰老师对闭包的讲解,点击可查看

理解闭包首先要理解js的变量作用域:全局变量和局部变量
1、作用域
js中函数内部可以直接读取全局变量,函数外部无法读取函数内部的局部变量:

var a = 100;   // 全局变量
function fn(){
    console.log(a);    // 读取全局变量
}
fn();    // 100

*************************************************

function fn(){
    var a = 100;   // 函数内部局部变量     
}
console.log(a);    //a is not defined   报错 访问函数内部的局部变量

在局部变量中声明的变量一定要用var 关键字,不使用var关键字就会变成全局变量:

fucntion fn(){
    a = 100;
}fn()
console.log(a);   // 100

闭包的概念:闭包是指有权访问另一个函数作用域中的变量的函数。(js高级程序设计3版的解释)
创建闭包的常见方式,就是在一个函数内部创建另一个函数;

2、外部读取局部变量
正常情况下外部是无法得到函数内部的局部变量,要想得到函数的局部变量,就需要在函数内部在定义一个函数:

function f1(){
    var n = 100;
    function f2(){
        console.log(n);    // 100
    }
}

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了!

function f1(){
    var n = 100;
    function f2(){
        console.log(n);    // 100
    }
    return f2;
}
var a = f1();
a();   // 100

2、闭包的概念
闭包:是指有权访问另一个函数作用域中的变量函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。
一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。
但是,闭包的情况又有所不同。
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。

3、闭包的用途
它的最大用处有两个,一个是读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

function f1(){
    var n=999;
	nAdd=function(){n+=1}
   function f2(){
	 alert(n);
	}
    return f2;
}

var result=f1();
result(); // 999    // result实际上就是闭包f2函数
nAdd();
result(); // 1000
// 在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是
1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

//为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,
而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

//这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用
var关键字,因此nAdd是一个全局变量,而不是局部变量。
其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,
可以在函数外部对函数内部的局部变量进行操作。

4、闭包的缺点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

四、闭包中的this对象
在闭包中使用 this 对象也可能会导致一些问题。我们知道, this 对象是在运行时基于函数的执行环境绑定的:在全局函数中, this 等于 window ,而当函数被作为某个对象的方法调用时, this 等于那个对象。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window 。

var name = "the windw";
var obj = {
    name:"my obj",
    getNameFun:function(){
          return function(){      // 匿名函数,执行环境是全局,
              return this.name;   // the windw     因为在匿名函数里,执行环境是全局,所以this指向的是window
          }
    }
}
console.log(obj.getNameFun()());   //  the windw

为什么匿名函数没有取得其包含作用域(或外部作用域)的 this 对象呢???
每个函数在被调用时都会自动取得两个特殊变量: this 和 arguments 。内部函
数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。不过,把外部作用域中的 this 对象保存在一个闭包能够访问
到的变量里,就可以让闭包访问该对象了,如下所示。

var name = "the windw";
var obj = {
    name:"my obj",
    getNameFun:function(){
          var that = this;    //   外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象
          return function(){      // 匿名函数,执行环境是全局,
              return that.name;   // my obj     在定义匿名函数之前,我们把 this对象赋值给了一个名叫 that 的变量。而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声名的一个变量。即使在函数返回之后, that 也仍然引用着 object ,所以调用object.getNameFunc()() 就返回了 "My Object" 。
          }
    }
}
console.log(obj.getNameFun()());   //  my obj
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值