定义函数的方式:
1.函数声明
function functionName(arg0,arg1,arg2){ //函数体 }
特征:函数声明提前,在执行代码之前会读取函数声明,意味着可以把函数声明放在调用它的语句后面。
2.函数表达式
var funtionName = function(arg0,arg1,arg2){ //函数体 }
匿名函数,创建一个函数并将它赋值给变量functionName,在使用之前必须赋值。
匿名函数的用途:
1.递归
递归函数是在一个
函数通过名字调用自身的情况下构成的。
funtion factorial(num){
if(num <=1){ return 1}
else return{ num * factorial(num-1) }
}
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));//出错,factorial不再是函数
arguments.callee是一个指向正在执行的函数的指针,可以实现递归调用:
return num * arguments.callee(num-1);
但在严格模式下,不能通过脚本访问arguments.callee,可以使用
命名函数表达式:
var factorial = (function f(num){
if(num <=1) return 1;
else return num*f(num-1);
})
2.闭包
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方法,就是在一个函数内部创建另一个函数。
function createComparisonFuncion(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;
}
}
函数如何被调用:当某个函数被调用时,会
创建一个执行环境及相应的作用域链,然后
使用arguments和其他命名参数的值来初始化函数的活动对象,但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,直至作为作用域链终点的全局执行环境。
如何构建作用域链:
在创建函数时,会创建一个预先包含全局变量对象(表示变量的对象)的作用域链,这个作用域链被保存在内部的[[scope]]属性中,在调用函数时,会为函数创建一个执行环境,通过复制函数的[[scope]]属性中的对象构建起执行环境的作用域链,此后,又有一个活动对象被创建并被推入执行环境作用域链的前端。
作用域链的本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),
闭包则在另一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中。
注:由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。
2.1闭包与变量
闭包只能取得包含函数中任何变量的最后一个值。
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;//每个函数都返回10
}
每个函数的作用域链中都保存着createFunction()函数活动对象,所以它们引用的都是同一个变量i,当函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以每个函数内部i的值都是10。
解决方法:可以通过
创建另一个匿名函数强制让闭包的行为符合预期:
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){//result数组中的每个函数都有自己num变量的一个副本
return function(){
return num;
};
}(i);
}
return result;
}
2.2关于this对象
this 对象是在运行时基于函数的执
行环境绑定的:在全局函数中, this 等于window,而当函数被作为某个对象的方法调用时, this 等
于那个对象。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。
解决方法:
把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
2.3内存泄漏
闭包在IE中会导致:如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){//无法减少element的引用数,只要匿名函数存在,element的引用数至少为1,它所占用的内存永远不会被回收
alert(element.id);
};
}
解决方法:将element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。
闭包会引用包含函数
的整个活动对象,而其中包含着
element
。即使闭包不直接引用
element
,包含函数的活动对象中也
仍然会保存一个引用。因此,有必要把
element
变量设置为
null
。这样就能够解除对
DOM
对象的引
用,顺利地减少其引用数,确保正常回收其占用的内存。
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
3.模仿块级作用域
在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
没有块级作用域的问题:在语句块中定义的变量在函数内部随处可以访问,即使错误地重新声明一个变量,也不会改变它的值。
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //计数
}
解决办法:使用匿名函数模块块级作用域(私有作用域),在匿名函数中定义任何变量,都会在执行结束时被销毁,语法如下。
function关键字当作一个函数声明的开始,要
将函数声明转换成函数表达式
(function(){
//块级作用域
})()
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); //导致一个错误!
}
4.私有变量
在函数内部创建一个闭包,那么闭包通过自己的作用域链可以访问变量,就可以创建用于访问私有变量和私有函数的公有方法(特权方法)。
方式一:在构造函数中定义特权方法,利用私有和特权成员,可以隐藏不应该被直接修改的数据
function MyObject(){
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function(){
privateVariable++;
return privateFunction();
}//在创建MyObject的实例后,除了使用publicMethod这一个途径外,没有任何办法可以直接访问privateVariable和privateFunction
}
4.1静态私有变量
方式二:通过在私有作用域中定义私有变量或函数
(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//构造函数,使用了函数表达式(函数声明只能创建局部函数)
MyObject = function(){
};//初始化未经声明的变量,总会创建一个全局变量,所以MyObject是全局变量,能够在私有作用域之外被访问到
//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
与方式一的区别:私有变量和函数是由实例共享的。
由于
特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法,作为一个闭包,总是
保存着对包含作用域的引用。
对象的方法可以直接调用,在原型链上的方法需要实例化才可以调用。
(function(){
var name = "";
Person = function(value){
name = value;
};
Person.prototype.getName = function(){
return name;
};
Person.prototype.setName = function (value){
name = value;
};
})();
var person1 = new Person("Nicholas");
alert(person1.getName()); //"Nicholas"
person1.setName("Greg");
alert(person1.getName()); //"Greg"
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"
- //在一个实例上调用 setName()会影响所有实例。而调用 setName()或新建一个 Person 实例都会赋予 name 属性一个新值。结果就是所有实例都会返回相同的值
4.2模块模式
为单例创建私有变量和特权方法。
所谓单例(singleton),指的就是只有一个实例的对象。
按照惯例, JavaScript 是以对象字面量的方式来创建单例对象的。
以对象字面量的方式来创建单例对象:
var singleton = {
name : value,
method : function () {
//这里是方法的代码
}
};
模块模式通过为单例添加私有变量和特权方法能够使其得到增强,其语法形式如下:
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权/公有方法和属性
return {//匿名函数
publicProperty: true,
publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();
这个
对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有
变量时是非常有用的,例如:
var application = function(){//管理组件的对象
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//有权访问数组components的特权方法
return {
getComponentCount : function(){
return components.length;
},
registerComponent : function(component){
if (typeof component == "object"){
components.push(component);
}
}
};
}();
如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。
4.3增强的模块模式
改进:在返回对象之前加入对其增强的代码,适合
单例必须是某种类型的实例,同时还必须添加某些属性或方法对其加以增强的情况。
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//创建对象
var object = new CustomType();
//添加特权/公有属性和方法
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
};
//返回这个对象
return object;
}();
如果前面演示模块模式的例子中的 application 对象必须是 BaseComponent 的实例,那么就可
以使用以下代码。
var application = function(){
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//创建 application 的一个局部副本
var app = new BaseComponent();
//公共接口
app.getComponentCount = function(){
return components.length;
};
app.registerComponent = function(component){
if (typeof component == "object"){
components.push(component);
}
};
//返回这个副本
return app;
}();
5.小结
在 JavaScript 编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名,
从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用 JavaScript 函数的强大方式。以下总结
了函数表达式的特点。
- 函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫做匿名函数。
- 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂;
- 递归函数应该始终使用 arguments.callee 来递归地调用自身,不要使用函数名——函数名可能会发生变化。
当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理如下:
- 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
- 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
- 但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。
- 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
- 结果就是函数内部的所有变量都会被立即销毁——除非将某些变量赋值给了包含作用域(即外部作用域)中的变量。
闭包还可以用于在对象中创建私有变量,相关概念和要点如下:
- 即使 JavaScript 中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。
- 有权访问私有变量的公有方法叫做特权方法。
- 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。
JavaScript 中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过,因为
创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。