先来回顾一下,定义函数的方式有两种:一种是函数声明,一种是函数表达式。
//使用函数声明创建函数
function functionName(arg0,arg1,...,argN){
//函数体
}
//使用函数表达式创建函数
var functionName = function(arg0,arg1,...,argN){
//函数体
};
关于函数声明,有一个重要的特征就是函数声明提升,即在执行代码之前会先读取函数声明,这就意味着可以把函数声明放在调用它的语句后面。
而对于函数表达式而言,在使用之前必须先赋值。
这里就要谈到一个概念,即匿名函数。
创建一个函数并将它赋值给一个变量,这种情况创建的函数叫做匿名函数,有时也叫拉达姆函数。
匿名函数的name属性是空字符串。
在把函数当成值来使用的情况下,都可以使用匿名函数。
一、递归
递归函数是在一个函数通过名字调用自身的情况下构成的。
function factorial(num){
if (num <= 1){
return 1;
}
else {
return num * arguments.callee(num-1);
}
}
其中,arguments.callee是一个指向正在执行的函数的指针,可以用它来实现对函数的递归调用。使用arguments.callee代替函数名,在调用时不会出现问题。
二、闭包
1、闭包的概念
闭包是指有权访问另一个函数作用域中的变量的函数。
//函数内部可以直接读取全局变量
var a = 17;
function f1(){
alert(a);
}
f1(); // 17
//在函数外部无法访问函数内的局部变量
function f2(){
var b = 16;
}
alert(b); // 出现错误!
function f3(){
c = 15;
}
f3();
alert(c); // 15
再看下面这个例子。
var result = function(){
var n = 10;
function func(){
alert(n);
}
return func();
}
result(); // 10
我们知道,在JavaScript语言中,函数可以访问函数外部的变量,那么要想访问另一个函数的变量,只能是函数的子函数,这样的话,可以将闭包简单理解为定义在一个函数内部的函数。
在上面这个例子中,func函数就是闭包,而赋值给result的那个函数就是匿名函数。
由此可以知道,创建闭包的方式是:在一个函数内部创建另一个函数。
2、闭包与变量
闭包只能取得包含函数中任何变量的最后一个值。
闭包保存的是整个变量对象,而不是某个特殊的变量。
关于这两句话的理解可以看下面这个例子。
function createFunction(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
var func = createFunction();
for(var j=0; j < func.length;j++){
document.write(func[j]() + "<br>");
}
输出结果为10个10。这是因为每个函数的作用域链中都保存着createFunction()函数的活动对象,所以它们引用的都是同一个变量i。当createFunction()函数返回后,变量i值是10,此时每个函数都引用着保存变量i的同一个变量对象。
如果希望位置0的函数返回0,位置1的函数返回1,则可以通过创建另一个匿名函数强制让闭包的行为符合预期。
function createFunction(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
var func = createFunction();
for(var j=0; j < func.length;j++){
document.write(func[j]() + "<br>");
}
在这里,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数num,也就是最终放入函数要返回的值。在调用每个匿名函数时,传入变量i,然后将变量i的值复制给参数num。而在这个匿名函数内部,又创建并返回了一个访问num的闭包。这样一来,result数组中的每个函数都有自己num变量的一个副本,就可以返回各自不同的数值了。
3、关于this对象
this对象是运行时基于函数的执行环境绑定的:
- 在全局函数中,this等于window;
- 当函数被作为某个对象的方法调用时,this等于那个对象。
匿名函数的执行环境具有全局性,所以this对象通常执行window,但是在通过call()或apply()改变函数执行环境的情况下,this就会指向其他对象。
var name = "The Window";
var object = {
name: "My Object",
getName: function(){
return function(){
return this.name;
};
}
};
alert(object.getName()()); // The Window
在每个函数调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,所以永远不可能直接访问外部函数中的这两个变量。但是,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。
var name = "The Window";
var object = {
name: "My Object",
getName: function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getName()()); // My Object
3、内存泄漏
一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是闭包的情况有所不同。在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。
具体来说,如果闭包的作用域保存着一个HTML元素,那么该元素将无法销毁。
4、使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变函数内部变量的值。
三、模仿块级作用域
1、JavaScript没有块级作用域的概念,这意味着在块语句中定义的变量,实际上是在包含函数中而非在语句中创建的。
2、JavaScript不会告诉你是否多次声明了同一个变量,它只会对后续声明视而不见,但是它会执行后续声明中的变量初始化。
3、匿名函数可以用来模仿块级作用域(私有作用域),语法如下:
(function)(){
//这里是块级作用域
})();
将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。
function(){
//这里是块级作用域
}(); //出错!
上面这段代码会导致语法错误,是因为JavaScript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。但是,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要加上一对圆括号即可。
【注】在匿名函数中定义的任何变量,都会在执行结束时被销毁。
4、用途与好处
一般来说,应该尽量少向全局作用域中添加变量和函数。而这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。
通过创建私有作用域,每个开发人员既可以使用自己的变量,又不必担心搞乱全局作用域。
这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就立即销毁其作用域链。
四、私有变量
JavaScript中没有私有成员的概念,所有对象属性都是公有的。但是有私有变量的概念。
私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
1、特权方法:有权访问私有变量和私有函数的公有方法。
创建特权方法的方式:
1)在构造函数中定义特权方法——特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。
function MyObject(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function (){
privateVariable++;
return privateFunction();
};
}
2)在私有作用域中定义私有变量和函数。
(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//构造函数
MyObject = function(){
};
//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
这个模式与构造函数中定义特权方法的主要区别在于,私有变量和函数是有实例共享的。
2、模块模式:为单例创建私有变量和特权方法。
单例,指的是只有一个实例的对象。
var singleton = {
name: value,
method: function(){
//这里是方法的代码
}
};
模块模式通过为单例添加私有变量和特权方法能够使其得到增强,其语法形式如下:
var singleton = function(){
//私有变量和私有函数
var privateFunction(){
return false;
}
//特权/公有方法和属性
return {
publicProperty: true,
publicMethod: function(){
privateVariable++;
return privateFunction();
}
};
}();
3、增强的模块模式
- 在返回对象之前加入对其增强的代码。
- 适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。