函数表达式

JavaScript中创建函数主要有两种方法:函数声明和函数表达式。这两种方式都有不同的适用场景。这里主要介绍函数表达式的应用场景。

一.函数递归:是在一个函数通过调用名字调用自身的情况下构成的

看下面的例子:

1
2
3
4
5
6
7
function factorial(num){
    if(num <= 1){
         return 1;
    }else{
         return num * factorial(num - 1);
    }
}

  这是一个经典的阶乘函数,但是这个例子存在的一个问题是函数名称factorial与函数体紧密耦合在一起,执行下面的语句就会报错:

1
2
3
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(5));   //"Uncaught TypeError: factorial is not a function"

  报错的原因在于在函数体内部会调用factorial函数,而变量factorial对函数的引用已经被解除所以报错。这种情况的解决方法一般可以使用arguments.callee来解决,arguments.callee始终指向当前的函数,例如:

1
2
3
4
5
6
7
function factorial(num){
    if(num <= 1){
         return 1;
    }else{
         return num * arguments.callee(num - 1);
    }
}

  这样在此执行anotherFactorial函数就可以得到正确结果了。但是在严格模式"strict"下,arguments.callee是不能通过脚本访问的,这是就可以使用函数表达式来解决这个问题了,例如:

 
1
2
3
4
5
6
7
8
var factorial = (function f(num){
    if(num <= 1){
         return 1;
    }else{
         return num * f(num - 1);
    }
});
console.log(factorial(5));   //"120"

 

二.闭包:是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数 。

作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可以清晰地说明这个问题。

这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置 0 的函数返回 0,位置 1 的函数返回 1,以此类推。但实际上,每个函数都返回 10。因为每个函数的作用域链中都 保 存 着 createFunctions() 函 数 的 活 动 对 象 , 所 以 它 们 引 用 的 都 是 同 一 个 变 量 i 。 当createFunctions()函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量对象,所以在每个函数内部 i 的值都是 10。但是,我们可以通过创建另一个匿名函数强制让闭包的行为符合预期,如下所示。

在重写了前面的 createFunctions()函数后,每个函数就会返回各自不同的索引值了。在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数 num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量 i。由于函数参数是按值传递的,所以就会将变量 i 的当前值复制给参数 num。而在这个匿名函数内部,又创建并返回了一个访问 num 的闭包。这样一来, result 数组中的每个函数都有自己num 变量的一个副本,因此就可以返回各自不同的数值了。

闭包耗费内存的原因:闭包作用域链引用了外部函数的变量对象,闭包没有执行完毕,作用域链引用的外部函数的变量对象都不会被回收,闭包中不需要使用的变量尽早解除引用

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

闭包在访问外部变量、this、引用HTML元素的时候需要注意:
1.闭包只能取得包含函数中任何变量的最后一个值
2.如果闭包的外部环境是一个函数,那么闭包中的this是window,如果闭包的外部环境是object,那么this是这个对象
3.闭包在引用HTML元素时,一旦HTML元素被引用,那么该元素将无法被销毁(内存泄露)

闭包的应用场景:自执行函数、私有属性和方法以及采用函数引用方式的setTimeout调用

三.模仿块级作用域:JavaScript中是没有块级作用域概念的。也就是说,在块级语句中定义的变量,实际上是在包含函数中(外部函数)而非语句中创建的。
1
2
3
4
5
6
function outputNumber(count){
  for(var i=0;i<1000;i++){
    alert(i);
  }
  alert(i);  //count
}

该函数在java、C#等语言中,变量i只会在for循环语句中有定义,循环结束,i也就被销毁了。但在JavaScript中,变量i是定义在outputNumber()活动对象中的,因此在它定义开始,就可以在函数内部访问它。即使重新声明同一个变量,也不会改变它的值。

匿名函数可以用来模仿块级作用域并避免这个问题,用作块级作用域(也称私有作用域)的匿名函数的语法如下:

1
2
3
(function(){
   //这是块级作用域
})()

以上代码定义变调用了一个匿名函数,将函数声明包含在一个小括号里面,表示它是个函数表达式。紧跟其后的另一对小括号会立即调用这个函数。

 

http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

 
posted @ 2017-10-11 18:46  把酒祝东风19  阅读(143)  评论(0)    收藏  举报