第十章(二)
本章内容:
- 使用函数实现递归
- 使用闭包实现私有变量
10.11 函数表达式
函数表达式看起来就像一个普通的变量定义和赋值,即创建一个函数再把它赋值给一个变量functionName。这样创建的函数叫作匿名函数(anonymous funtion),因为function 关键字后面没有标识符。(匿名函数有也时候也被称为兰姆达函数)。未赋值给其他变量的匿名函数的name 属性是空字符串。
let functionName = function(arg0, arg1, arg2) {
// 函数体
};
理解函数声明与函数表达式之间的区别,关键是理解提升。比如,以下代码的执行结果可能会出乎
意料:
// 千万别这样做!
if (condition) {
function sayHi() {
console.log('Hi!');
}
} else {
function sayHi() {
console.log('Yo!');
}
}
如果把上面的函数声明换成函数表达式就没问题了:
// 没问题
let sayHi;
if (condition) {
sayHi = function() {
console.log("Hi!");
};
} else {
sayHi = function() {
console.log("Yo!");
};
}
10.12 递归
递归函数通常的形式是一个函数通过名称调用自己。
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
这是经典的递归阶乘函数。虽然这样写是可以的,但如果把这个函数赋值给其他变量,就会出问题:
let anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(4)); // 报错
在写递归函数时使用arguments.callee 可以避免这个问题。
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
不过,在严格模式下运行的代码是不能访问arguments.callee 的,因为访问会出错。此时,可以使用命名函数表达式(named function expression)达到目的。
10.13 尾调用优化
ECMAScript 6 规范新增了一项内存管理优化机制,让JavaScript 引擎在满足条件时可以重用栈帧。具体来说,这项优化非常适合“尾调用”,即外部函数的返回值是一个内部函数的返回值。比如:
function outerFunction() {
return innerFunction(); // 尾调用
}
即外部函数的返回值是一个内部函数的返回值时,因为返回值相同所以可以直接将outer函数弹出栈外,入栈inner函数进行计算返回后弹出。
10.13.1 尾调用优化条件
P307
差异化尾调用和递归尾调用是容易让人混淆的地方。无论是递归尾调用还是非递归尾调用,都可以应用优化。引擎并不区分尾调用中调用的是函数自身还是其他函数。不过,这个优化在递归场景下的效果是最明显的,因为递归代码最容易在栈内存中迅速产生大量栈帧。
注意
之所以要求严格模式,主要因为在非严格模式下函数调用中允许使用f.arguments和f.caller,而它们都会引用外部函数的栈帧。显然,这意味着不能应用优化了。因此尾调用优化要求必须在严格模式下有效,以防止引用这些属性。
10.13.2 尾调用优化的代码
可以通过把简单的递归函数转换为待优化的代码来加深对尾调用优化的理解。下面是一个通过递归计算斐波纳契数列的函数:
function fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n -</