函数表达式
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

浙公网安备 33010602011771号