JS函数表达式

递归

递归函数是在一个函数通过名字调用自身

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

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial (4));//出错

将factorial()函数保存在变量anotherFactorial中,然后将 factorial变量设置为null,在调用anotherFactorial()时,需要执行factorial(),而factorial已经不是函数,所以会发生错误。

function factorial(num){
   if(num<=1){
      return 1;
   }else{
      return num*argument.callee(num-1);
   }  
}
//argument.callee是指向正在执行的函数的指针

使用argument.callee是指向正在执行的函数的指针比使用函数名更保险,但在严格模式下,不能通过脚本访问argument.callee

function factorial(function f(num){
   if(num<=1){
      return 1;
   }else{
      return num*f(num-1);
   }  
});
//argument.callee是指向正在执行的函数的指针

创建一个名为f()的命名函数表达式,然后赋值给变量factorial。这样f永久有效,所以递归可以正确完成。这种方式在严格模式和非严格模式都可以使用。

闭包

闭包指有权访问另一个函数作用域中的变量的函数。

function createComparisonFunction(propertyName){
   return function(object1,object2){
      var value1 = object1[propertyName];
      var value2 = object2[propertyName];
      if(value1<value2){
         return -1;
      }else if(value1>value2){
         return 1;
      } else{
         return 0;
      }
   };
}

var value1 = object1[propertyName];
var value2 = object2[propertyName];

这俩行代码访问了尾部函数中的变量propertyName,即使这个内部函数被返回了,而且在其他地方被调用了,但任然可访问变量propertyName。
当某一个函数第一次被调用时,会创建一个执行环境(execution context)及相应的作用域链,并把作用域链赋给一个特殊的内部属性([scope])。然后使用this,arguments和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,... ...直到作用域链终点的全局执行环境。

function compare(value1,value2){
   if(value1<value2){
       return -1;
   }else if (value1>value2){
       return 1;
   }else{
       return 0;   
   }
}
var result = compare(5,10);

后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,局部环境的变量对象,只在函数执行的过程中存在。
创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。
本例中作用域链中包含俩个变量对象:本地活动对象和全局变量对象。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
在函数中访问一个变量,就会从作用域链中搜索具有相应名字的变量。一般函数执行完毕,局部活动对象就会被销毁,内存中仅保存全局作用域,但闭包有所不同。
在一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中。因此在createComparisonFunction()函数内部定义的匿名函数的作用域链中,实际上将会包含外部函数createComparisonFunction()的活动对象

var compare = createComparisonFunction("name");
var result = compare({name:"Nicholas"},{name:"Greg"});

在匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。这样匿名函数就可以访问createComparisonFunction()中定义的所有变量。当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,单它的活动对象任然会保留在内存中,直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。

//创建函数
var comapareNames = createComparisonFunction("name");
//调用函数
//var result = compareName({name:"Nicholas"},{name:"Greg"});
//解除对匿名函数的引用(以便释放内存)
compareNames = null;

首先创建的比较函数被保存在变量compareNames 中,而通过将compareNames 设置为null解除该函数的引用,通知垃圾回收例程将其清除。匿名作用域链被销毁,其作用域也就可以安全地被销毁了如下图展示compareNames ()的过程中产生的作用域链之间的关系。


闭包会携带包含它的函数的作用域,会占用更多的内存。过度使用会导致内存使用过度,建议只在绝对必要时考虑使用闭包

闭包与变量

闭包只能取得包含函数中任何变量的最后哟个值。

function createFunctions(){
   var result = new Array();
   for (var i=0;i<10;i++){
      result[i]=function(){
         return i;
      }
   }
   return result;
}

函数返回一个函数数组,似乎每个函数都应该返回自己的索引值,但实际上,每个函数都返回10,因为每个函数的作用域中都保存着 createFunctions()函数的活动对象,所以他们引用的都是同一个变量i当createFunctions()返回后,i的值是10,所以函数内部i的值都是10,这时我们可以创建另一个匿名函数强制让闭包的行为符合预期

function createFunctions(){
   var result = new Array();
   for (var i=0;i<10;i++){
      result[i]=function(num){
         return function(){
            return num;
         };
      }(i);
   }
   return result;
}

我们没有直接把闭包赋值给数组,而是定义一个匿名函数,并立即执行该函数的结果赋值给数组。在匿名函数的内部又创建并返回访问num的闭包,这样result数组中的每个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值啦。

关于this对象

this对象时在运行时基于函数的执行环境绑定的,匿名函数的执行环境具有全局性,因此对象通常指向windows。

var name = "The window";
var object = {
   name : "MyObject",
   getNameFunc : function(){
      return function(){
            return this.name;
      };     
   }
}
alert(object.getNameFunc()());//"The window"(在非严格模式下)

每个函数在被调用时,其活动对象都会自动取得俩个特殊的变量:this和argument。内部函数在搜索这俩个变量时,只会搜索到其活动对象为止,因此不可能直接访问外部函数中的这个变量。如果把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。

var name = "The window";
var object = {
   name : "MyObject",
   getNameFunc : function(){
      var that =this;
      return function(){
         return that.name;
      };
   }
}
alert(object.getNameFunc()());//"My Object"

在函数返回之后,that也仍然引用object,所以调用object.getNameFunc()())就返回“My Object”。

var name = "The window";
var object = {
   name : "MyObject",
   getName :function(){
      return that.name;
   }
};

object.getName();   //"My Object"
(bject.getName)();   //My Object"  
object.getName和(bject.getName)的定义相同
(object.getName() = object.getName)(); //"The windows"在非严格模式下
赋值表达式的值是函数本身,所以this的值得不到维持,结果就返回了"The window"

内存泄漏

如果在闭包的作用域链中保存着一个HTML元素,那么该元素将无法被销毁

function assignHandler(){
   var element = document.getElementById("someElement");
   element.onclick = function(){
      alert(element.id);
   }
}

由于函数保存了一个对assignHandler()的活动对象的引用,因此会导致无法减少element的引用数。只要匿名函数存在,element的引用至少是1,因此他所占的内存就不会被回收。

function assignHandler(){
   var element = document.getElementById("someElement");
   var id = element.id;
   element.onclick = function(){
      alert(id);
   };
   element = null;
}

通过把element.id的一个副本保存在变量中,并且在闭包中引用该变量消除了循环引用。
闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不直接引用element包含函数的活动对象中也任然会保存一个引用。把element设置为null。这样就能够解除对DOM对象的引用,确保回收其占用的内存。

推荐阅读更多精彩内容