引言:函数表达式是JavaScript 中一个既强大又容易令人困惑的特性。这一章我们着重对JavaScript 的函数进行总结学习,内容包括:JS的函数特性,以及闭包、this 对象等。不得不说,《JavaScript高级程序设计第三版》这本书中最让我觉得纠结的就是这一章节了,前面或者是后面的章节学习不是没有难点,但是无论是这一章的“this” 还是“JavaScript 的块级作用域(注意我这里说的是JavaScript的)”以及闭包等等知识在学习的时候都很容易让人糊涂,比如一个“this”的作用,不同的应用场景,this 的指向又有所不同,在学习this的时候我总是会感觉 this 的运用有些莫名其妙,所以这一章的总结,我想我会尽量去进行这些知识点的举例,通过更多的例子来进行说明、搞懂这些知识点。
函数声明提升:
关于函数声明,它的一个重要的特征就是“函数声明提升” 意思是在执行代码前会先读取函数声明,这就意味着可以把函数声明放在调用它的语句后面。比如:
sayHi();
function sayHi(){
alert("Hi");
}
这个例子不会报错,因为在代码执行前会先读取函数声明。(这里不得不说的是,JavaScript 中对于var 声明的变量也有“变量提升”——函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部)。这其实也归功于JavaScript 的解析机制:遇到 script 标签的话 js 就进行预解析,将变量 var 和 function 声明提升,但不会执行 function,然后就进入上下文执行,上下文执行还是执行预解析同样操作,直到没有 var 和 function,就开始执行上下文。需要注意都是函数声明提升直接把整个函数提到执行环境的最顶端。
第二种创建函数的方式是使用函数表达式。函数表达式有几种不同的语法形式。下面是最常见的一 种形式。
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!"); } }
一个定义。实际上,这在 ECMAScript 中属于无效语法,JavaScript 引擎会尝试修正错误,将其转换为合 理的状态。但问题是浏览器尝试修正错误的做法并不一致。大多数浏览器会返回第二个声明,忽略 condition;Firefox 会在 condition 为 true 时返回第一个声明。因此这种使用方式很危险,不应该 出现在你的代码中。不过,如果是使用函数表达式,那就没有什么问题了。
1 //可以这样做 2 var sayHi; 3 if(condition){ 4 sayHi = function(){ 5 alert("Hi!"); 6 }; 7 } else { 8 sayHi = function(){ 9 alert("Yo!"); 10 }; 11 }
函数递归:
所谓递归函数,通俗理解就是函数调用自己本身。比如:
1 function factorial(num){ 2 if (num <= 1){ 3 return 1; 4 } else { 5 return num * factorial(num-1); 6 } 7 }
这是一个经典的递归阶乘函数。虽然这个函数表面看来没什么问题,但下面的代码却可能导致它出错。
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //出错!
以上代码先把 factorial()函数保存在变量 anotherFactorial 中,然后将 factorial 变量设 置为 null,结果指向原始函数的引用只剩下一个。但在接下来调用 anotherFactorial()时,由于必 须执行 factorial(),而 factorial 已经不再是函数,所以就会导致错误。在这种情况下,使用 arguments.callee 可以解决这个问题。 我们知道,arguments.callee 是一个指向正在执行的函数的指针,因此可以用它来实现对函数 的递归调用,例如:
1 function factorial(num){ 2 if (num <= 1){ 3 return 1; 4 } else { 5 return num * arguments.callee(num-1); 6 } 7 }
加粗的代码显示,通过使用 arguments.callee 代替函数名,可以确保无论怎样调用函数都不会 出问题。因此,在编写递归函数时,使用 arguments.callee 总比使用函数名更保险。 但在严格模式下,不能通过脚本访问 arguments.callee,访问这个属性会导致错误。不过,可 以使用命名函数表达式来达成相同的结果。例如:
1 var factorial = (function f(num){ 2 if (num <= 1){ 3 return 1; 4 } else { 5 return num * f(num-1); 6 } 7 });
以上代码创建了一个名为 f()的命名函数表达式,然后将它赋值给变量 factorial。即便把函数 赋值给了另一个变量,函数的名字 f 仍然有效,所以递归调用照样能正确完成。这种方式在严格模式和 非严格模式下都行得通。
-------------------------------------------------------------------------------------本章节完-------------------------------------------------------------------------------
下章节预告:闭包与this。
(因为闭包与this常常被拿出来问,同时也是javascript中让人模糊的知识点,所以我打算好好在下一章节对其进行总结,这样一来,也比较方便于知识点的阅读)