JavaScript高级程序设计之函数表达式之递归第7.1讲笔记

来源:互联网 发布:java软件如何下载? 编辑:程序博客网 时间:2024/05/17 06:18
函数表达式是 JavaScript 中的一个既强大又容易令人困惑的特性。第5 章曾介绍过,定义函数的
方式有两种:一种是函数声明,另一种就是函数表达式。函数声明的语法是这样的。

function functionName(arg0, arg1, arg2) {//函数体}//只在Firefox、Safari、Chrome 和Opera 有效alert(functionName.name); //"functionName"


首先是 function 关键字,然后是函数的名字,这就是指定函数名的方式。Firefox、Safari、Chrome

和Opera 都给函数定义了一个非标准的name 属性,通过这个属性可以访问到给函数指定的名字。这个

属性的值永远等于跟在function 关键字后面的标识符。

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

sayHi();function sayHi(){alert("Hi!");}


这个例子不会抛出错误,因为在代码执行之前会先读取函数声明。
第二种创建函数的方式是使用函数表达式。函数表达式有几种不同的语法形式。下面是最常见的一
种形式。

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

这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName。
这种情况下创建的函数叫做匿名函数(anonymous function),因为function 关键字后面没有标识符。
(匿名函数有时候也叫拉姆达函数。)匿名函数的name 属性是空字符串。
函数表达式与其他表达式一样,在使用前必须先赋值。以下代码会导致错误。

sayHi(); //错误:函数还不存在var sayHi = function(){alert("Hi!");};
理解函数提升的关键,就是理解函数声明与函数表达式之间的区别。例如,执行以下代码的结果可
能会让人意想不到。

//不要这样做!if(condition){function sayHi(){alert("Hi!");}} else {function sayHi(){alert("Yo!");}}
表面上看,以上代码表示在condition 为true 时,使用一个sayHi()的定义;否则,就使用另
一个定义。实际上,这在ECMAScript 中属于无效语法,JavaScript 引擎会尝试修正错误,将其转换为合
理的状态。但问题是浏览器尝试修正错误的做法并不一致。大多数浏览器会返回第二个声明,忽略
condition;Firefox 会在condition 为true 时返回第一个声明。因此这种使用方式很危险,不应该
出现在你的代码中。不过,如果是使用函数表达式,那就没有什么问题了。

//可以这样做var sayHi;if(condition){sayHi = function(){alert("Hi!");};} else {sayHi = function(){alert("Yo!");};}

这个例子不会有什么意外,不同的函数会根据condition 被赋值给sayHi。
能够创建函数再赋值给变量,也就能够把函数作为其他函数的值返回。还记得第5 章中的那个
createComparisonFunction()函数吗:

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;}};}

createComparisonFunction()就返回了一个匿名函数。返回的函数可能会被赋值给一个变量,
或者以其他方式被调用;不过,在createComparisonFunction()函数内部,它是匿名的。在把函数

当成值来使用的情况下,都可以使用匿名函数。不过,这并不是匿名函数唯一的用途。

7.1 递归

递归函数是在一个函数通过名字调用自身的情况下构成的,如下所示。

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 已经不再是函数,所以就会导致错误。在这种情况下,使用arguments.
callee 可以解决这个问题。
我们知道,arguments.callee 是一个指向正在执行的函数的指针,因此可以用它来实现对函数
的递归调用,例如:

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

加粗的代码显示,通过使用arguments.callee 代替函数名,可以确保无论怎样调用函数都不会
出问题。因此,在编写递归函数时,使用arguments.callee 总比使用函数名更保险。
但在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误。不过,可
以使用命名函数表达式来达成相同的结果。例如:

var factorial = (function f(num){if (num <= 1){return 1;} else {return num * f(num-1);}});
以上代码创建了一个名为f()的命名函数表达式,然后将它赋值给变量factorial。即便把函数
赋值给了另一个变量,函数的名字f 仍然有效,所以递归调用照样能正确完成。这种方式在严格模式和
非严格模式下都行得通。

0 0
原创粉丝点击