Javascript中,函数表达式的定义有两种方法,一种是函数声明,一种是函数表达式。
函数声明:
function functionName(arg0,arg1,arg2){
//函数体
}
函数声明最重要的一个特征就是函数
声明提升,也就是在执行代码之前会先读取函数声明,也就意味着可以把函数声明放在调用它的语句后面。
函数表达式:
var functionName = function(arg0,arg1,arg2){
//函数体
}
函数表达式与其他的表达式一样,
在使用前必须先赋值。
下面举例说明函数声明与函数表达式的区别:
if(condition){
function sayHi(){
alert('Hi!');
}
}else {
function sayHi(){
alert('Yo!');
}
}
由于存在函数声明提升,上述代码属于无效代码,javascript引擎尝试修正错误,但浏览器修正错误的方法并不一致。不过,如果使用函数表达式,就不会有什么问题。
var sayHi;
if(condition){
sayHi = function(){
alert('Hi!');
}
}else {
sayHi = function(){
alert('Yo!');
}
}
上述代码中,不同的函数会根据condition被赋值给sayHi,不存在问题。
一、闭包
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式,就是在一个函数内部创建另一个函数。
当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后使用arguments和其他命名参数的值来初始化函数的活动对象。在作用域链中,函数的活动对象位于第一位,外部函数的活动对象位于第二位,外部函数的外部函数的活动对象位于第三位……直至作为作用域链终点的全局执行环境。
每个执行环境都有一个变量对象。全局环境的变量对象始终存在,而局部环境的变量对象,只在函数执行的过程中存在。作用域链被保存在执行环境内部的[[scope]]属性中,其本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
function compare(value1,value2){
if(value1<value2){
return -1;
}else if(value1>value2){
return 1;
}else{
return 0;
}
}
var result = compare(5,10);
上述代码代码中,先定义了一个compare函数,然后又在全局作用域中调用了它。当第一次调用compare()时,会创建一个包含this、arguments、value1、value2的活动对象。全局执行环境的变量对象(this、result、compare)在compare()的执行环境的作用域链中排第二位。compare()函数执行时的作用域链关系图如下:
一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但对于闭包来说,情况并不是这样的。
在另一个函数内部定义的函数会将外部函数的活动对象添加到自己的作用域链中。当内部函数被返回后,它的作用域链被初始化为包含外部函数的活动对象和全局变量对象。因此,被返回的内部函数照样可以访问在外部函数中定义的变量。更重要的是,外部函数执行完毕后,其活动对象也不会被销毁,因为内部函数的作用域链仍然在引用这个活动对象,知道内部函数被销毁,外部函数的活动对象才会被销毁。
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 comoare = createComparisonFunction('name');
var result = compare({name:'Nichrolas'},{name:'Greg'});
上述代码执行后,外部函数createComparisonFunction()与内部匿名函数的作用域链如下图:
作用域链存在的一个问题是,闭包只能取得外部函数中任何变量的最后一个值。如下例所示:
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(){
return i;
};
}
return result;
}
这个函数会返回一个函数数组。数组中的每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以数组中的每一个函数都引用的是同一个变量i,所以,当createFunctions()函数返回后,变量i的值是10,所以调用数组中的每个函数都会返回10。
可以通过创建一个匿名函数强制闭包的行为达到预期的效果。
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
在上述代码中,没有直接把闭包赋值给数组,而是将匿名函数的结果赋值给了数组。在调用每个匿名函数的时候,传入参数i,由于函数参数是按值传递的,所以会将变量i的当前值复制给参数num。而在匿名函数内部,会创建并返回一个访问num的闭包。因此,result数组中的每个函数都有自己num变量的一个副本。