JavaScript 高级程序设计-第七章-函数表达式
函数表达式的特征
定义函数有两种方式:
- 函数声明:
function functionName(arg0, arg1, arg2){
}
//只在 Firefox、Safari、Chrome 和 Opera 有效
alert(functionName.name); //"functionName"
函数声明有一个特征是函数声明提升,详见笔记—《JS 变量和函数的声明提升》。
- 函数表达式,最常见的函数表达式写法:
var functionName = function(arg0, arg1, arg2){
}
创建一个函数并赋给一个变量,很像常规的变量赋值语句,赋给那个变量的函数叫匿名函数(有时匿名函数也叫拉姆达函数),匿名函数的 name 属性是空字符串。
函数声明和函数表达式的区别详见笔记—《JS 变量和函数的声明提升》。
使用函数实现递归
递归函数是一个函数调用自身的构成的,一个经典的递归阶乘函数,但是会报错!,例子:
function factorial(num) {
if (num <= 1) {
return 1
} else {
return num * factorial(num - 1)
}
}
var anotherFactorial = factorial
factorial = null
alert(anotherFactorial(4)) // 出错!
报错原因是,factorial = null
这一步使指向原始函数的引用就一个,但在执行 anotherFactorial 函数时需要调用 factorial 函数,但此时 factorial 为 null,所以报错(当然这里直接执行 factorial(4) 是没问题的)。
arguments.callee 是一个指向正在执行的函数的指针,用它来实现函数的递归调用,例子:
function factorial(num) {
if (num <= 1) {
return 1
} else {
return num * arguments.callee(num - 1)
}
}
var anotherFactorial = factorial
factorial = null
alert(anotherFactorial(4)) // 12
警告:在严格模式下,第5版 ECMAScript (ES5) 禁止使用
arguments.callee()。当一个函数必须调用自身的时候, 避免使用 arguments.callee()
,要么给函数表达式一个名字,要么使用一个函数声明。
例子修改为:
var factorial = (function f(num) {
if (num <= 1) {
return 1
} else {
return num * f(num - 1)
}
})
这种方式在严格模式和非严格模式下都行得通。
使用闭包定义私有变量
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式是在一个函数中创建另一个函数,例子:
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
}
}
}
var compareNames = createComparisonFunction("name")
var result = compareNames({ name: "Nicholas" }, { name: "Greg" }) // result===1
//解除对匿名函数的引用(以便释放内存)
compareNames = null;
内部函数之所以能访问外部函数的参数,是因为当一个函数被调用时会创建一个执行环境和相应的作用域链,作用域链的起点是被调用函数的活动对象(arguments 和命名参数),外部函数的活动对象在第二位,再外部函数的活动对象在第三位,直到作用域链的终点(全局执行环境)。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
一般来说函数执行完毕,局部活动对象会被销毁,内存只保留全局作用域。但是闭包不同,例子中匿名函数的作用域链为匿名函数的活动对象—> createComparisonFunction 函数的活动对象—> 全局执行环境。因此匿名函数可以访问 createComparisonFunction 函数的 propertyName 参数,而且在匿名函数被赋给变量 compareNames 后(此时 createComparisonFunction 函数已经执行完了)createComparisonFunction 函数的活动对象不会被销毁,因为它还在匿名函数的作用域中。如例子所示,销毁对匿名函数的引用,createComparisonFunction 函数的活动对象才会被销毁。
虽然像 V8 等优化后的 JavaScript 引擎会尝试回收被闭包占用的内存,但请大家 还是要慎重使用闭包。
闭包与变量
经典的闭包例子:
function createFunctions(){
var result = new Array();
for (var i=0; i < 3; i++){
result[i] = function(){
return i;
};
}
console.log(result[0](),result[1](),result[2]());
}
createFunctions() // 3 3 3
次数为3次的遍历中的每一个匿名函数都作用域链的第二位都是 createFunctions() 的活动对象,引用的是同一个变量 i,程序运行结束的时候 i 是3,所以输出的结果都是3。
修改后:
function createFunctions(){
var result = [];
for (var i=0; i < 3; i++){
result[i] = function(num){
return function(){
return num;
}
}(i)
}
console.log(result[0](),result[1](),result[2]());
}
createFunctions() // 0 1 2
每一个匿名函数的作用域链的第二位是有着参数为 num 的立即执行的匿名函数的活动对象,num 参数是由处于作用域链第三位的 createFunctions() 的 i 值传递传递进来的,因此最后输出的是 0 1 2。
关于 this 对象
- this 对象是运行时基于函数的执行环境绑定的;
- 在全局环境中 this 等于 window,当函数作为某个对象的方法被调用时,this 等于这个对象;
- 匿名函数的执行环境有全局性,this 等于 window。
var name = 'The Window'
var object = {
name: 'My Object',
getName: function () {
// var that = this; 如果下面的 this 改成 that 的话就可以指向 object 对象
// 每个函数调用时都会自动获得 this 和 arguments,这里的 this 还是指向 object,下面匿名函数的 this 就指向全局了
return function () {
return this.name
}
}
}
console.log(object.getName()()) // "The Window"
var name2 = 'The Window'
var object2 = {
name2: 'My Object',
getName2: function () {
return this.name2
}
}
console.log(object2.getName2()) // "My Object"
我的理解是第一个最终调用的匿名函数,匿名函数执行环境具有全局性,因此输出是”The Window”,第二个最终调用的 getName 方法是作为对象 object2 的方法被调用的,this 等于 object2,因此输出是“My Object”。