本节内容包括如下几点:
1、函数的声明和函数表达式的区别
2、什么是匿名函数
3、什么是闭包
4、关于在匿名函数中用this引发的问题
5、关于模仿块级作用域
函数的声明如下
function name(arg1,arg2){
//函数体
}
也可以以函数表达式的形式定义函数、如下:
var name=function(arg1,arg2){
//函数体
}
函数声明和函数表达式之间的主要区别:
1、函数声明会在代码执行之前被加载到作用域中,而函数表达式则是在代码执行到那一行的时候才会有定义。
2、函数的声明会给函数指定一个名字,而函数表达式则是创建一个匿名函数。
所以所谓匿名函数,就是没有名字的函数,有时候也称拉 姆达(lambda)函数。
例如:
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;
}
}
}
createComparisonFunction()就返回了一个匿名函数,返回的函数赋值给了一个变量。
闭包:是指有权访问另一个函数作用域中的变量的函数、闭包会引用包含函数的整个活动对象
例如上个实例蓝色文字标注的那两行代码是内部函数(一个匿名函数)中的代码,它们访问了外部函数中的变量propertyName,闭包所保存的是整个变量对象。
但是闭包有一个特点:只能取得包含函数中任何变量的最后一个值。
例如:
function createFunctions(){
var result=new Array();
for(var i=0;i<10;i++){
result[i]=function(){
return i;
}; //返回的始终会是10,因为闭包只能取得包含函数中任何变量的最后一个值。
}
return result;
}
var funs=createFunctions();
for(var i=0;i<funs.length;i++){
document.write(funs[i]()+"<br/>");
}
这个函数会返回一个数组,而且数组中的每个值都是10,如果要得到预期的结果即0、1、2...... 可以做如下变化
function createFunctions(){
var result=new Array();
result[i]=function(num){
return function(){
return num;
}
}(i);
}
return result;
}
var funs=createFunctions();
for(var i=0;i<funs.length;i++){
document.write(funs[i]()+"<br/>");
}
由于函数参数按值传递,所以会将变量i的当前值传递给参数num、而在这个匿名函数内部,又创建并返回了一个访问num的闭包。
灰色背景的代码页可以用如下代码替换:
result[i]=function(){
return i;
}(i);
关于this对象
在闭包中使用this对象会导致的一些问题、这是因为、匿名函数的执行环境具有全局性,因此其this对象通常指向window、举例如下:
var name="the window";
var object={
name:"this object",
getNameFunc:function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());//the window
结果返回the window、为什么匿名函数没有取得其包含作用域(或外部作用域)的this对象了?
因为每个函数在被调用时,它的活动对象会自动取得this和arguments这两个特殊的变量。内部函数在搜索这两个变量时,只会搜索到它的活动对象为止。
如果把外部作用域中的this对象保存在一个闭包能够访问到的变量里、就可以让闭包访问该对象。修改如下:
var name="the window";
var object={
name:"this object",
getNameFunc:function(){
var that=this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());//this object
this和arguments也存在同样的问题,如果想访问作用域中的arguments对象,必须将该对象的引用保存到另一个闭包能够访问的变量中。
关于内存泄露
如果闭包的作用域链中保存着一个html元素,则意味着该元素无法被销毁;例如:
function demoHandler(){
var element=document.getElementId("something");
element.οnclick=function(){
alert(element.id);
};
}
由于匿名函数中保存了一个对demoHandler()的活动对象的引用,因此只要匿名函数存在、element的引用数就大于等于1,因此它所占用的内存就永远不会回收。
可以进行如下优化:
function demoHandler(){
var element=document.getElementId("something");
var id=element.id;
element.οnclick=function(){
alert(id);
};
element=null;
}
关于模仿块级作用域
javascript没有块级作用域的概念,即在块级语句中定义的变量,实际上是在包含该语句的函数中创建的、例如:
function writeNumbers(count){
for(var i=0;i<count;i++){
alert(i);
}
alert(i);//count 即在块级语句之外也能够访问i
}
匿名函数可以用来模仿块级作用域
块级作用域(私有作用域)的匿名函数语法如下:
(function(){
//这里是块级作用域
})();
此段代码表示定义并立即调用了一个匿名函数。(最后面的那一对圆括号代表立即调用这个函数)
此语法可以用以下这种方式理解:
例如调用writeNumbers()函数的方式
可以是 var count=5; writeNumbers(count);
也可以直接 writeNumbers(5);
那么我们是不是也可以用函数的值直接来取代函数名字了?
var someFunction=function(){
//这里是块级作用域
};
someFunction();//定义一个匿名函数并赋值给变量someFunction,然后用调用函数的方式是在函数函数名称后面添加一对圆括号,即:someFunction();
如果综合以上的例子,是不是也可以这样调用
function(){
//这里是块级作用域
}(); //出错
这段代码会导致语法错误,是因为javascript将function关键字当做一个函数声明的开始,而函数的声明后面是不能够跟圆括号的。然而,函数表达式的后面是可以跟圆括号的。
因此,要将函数的声明转换为函数的表达式,只要在前面给它加一对圆括号即可、即变成了块级作用域的语句:
(function(){
//这里是块级作用域
})();
例如:
function outpubNum(count){
(function(){
for(var i=0;i< count;i++){
alert(i);
}
})();
alert(i);//在这里调用i会导致错误
}
这种技术可以限制向全局作用域中添加过多的变量和函数。