建议57:禁用Function构造函数
定义函数的方法包括3种:function语句、Function构造函数和函数直接量。不管用哪种方法定义函数,它们都是Function对象的实例,并将继承Function对象所有默认或自定义的方法和属性。
// 使用function语句编写函数
function f(x){
return x;
}
// 使用Function()构造函数克隆函数
var f = new Function("x", "return x;");
// 使用函数直接量直接生成函数
var f = function(x){
return x;
}
虽然这些方法定义函数的结构体相同,函数的效果相近,但是也存在很多差异,详细比较见表3.1。
表3.1 函数定义方法比较
使用function语句 使用Function构造函数 使用函数直接量
兼容 完全 JavaScript 1.1及以上 JavaScript 1.2及以上
形式 句子 表达式 表达式
名称 有名 匿名 匿名
主体 标准语法 字符串 标准语法
性质 静态 动态 静态
解析 以命令的形式构造一个函数对象 解析函数体,能够动态创建一个新的函数对象 以表达式的形式构造一个函数对象
(1)作用域比较
使用Function构造函数创建的函数具有顶级作用域,JavaScript解释器也总是把它作为顶级函数来编译,而function语句和函数直接量定义的函数都有自己的作用域(即局部作用域,或称为函数作用域)。例如:
var n = 1;
function f(){
var n = 2;
function e(){
return n;
}
return e;
}
alert(f()()); //2
在上面示例中,分别在函数体外和函数体内声明并初始化变量n,然后在函数体内使用function语句定义一个函数e,定义该函数返回变量n的值。最后在函数体外调用函数的返回函数。通过结果可以发现,返回值为局部变量n的值(即为2),也就是说,function语句定义的函数拥有自己的作用域。同理,如果使用函数直接量定义函数e,当调用该返回函数时,返回值是2,而不是1,那么也说明函数直接量定义的函数拥有自己的作用域。
但是,如果使用Function构造函数定义函数e,则调用该返回函数时,返回的值是1,而不再是2了,看来Function构造函数定义的函数作用域需要动态确定,而不是在定义函数时确定的,代码如下:
var n = 1;
function f(){
var n = 2;
var e = new Function("return n;");
return e;
}
alert(f()()); //1
(2)解析效率比较
JavaScript解释器在解析代码时,并非一行行地分析和执行程序,而是一段段地分析执行。在同一段代码中,使用function语句和函数直接量定义的函数结构总会被提取出来优先执行。只有在函数被解析和执行完毕之后,才会按顺序执行其他代码行。但是使用Function构造函数定义的函数并非提前运行,而是在运行时动态地被执行,这也是Function构造函数定义的函数具有顶级作用域的根本原因。
从时间角度审视,function语句和函数直接量定义的函数具有静态的特性,而Function构造函数定义的函数具有动态的特性。这种解析机制的不同,必然带来不同的执行效率,这种差异性可以通过将一个循环结构放大来比较得出。
在下面这个示例中,分别把function语句定义的空函数和Function构造函数定义的空函数放在一个循环体内,让它们空转十万次,这样就会明显感到使用function语句定义的空函数运行效率更高。
// 测试function语句定义的空函数执行效率
var a = new Date();
var x = a.getTime();
for(var i=0;i<100000;i++){
function(){ //使用function语句定义的空函数
;
}
}
var b = new Date();
var y = b.getTime();
alert(y-x); //62,不同环境和浏览器会存在差异
// 测试Function构造函数定义的空函数执行效率
var a = new Date();
var x = a.getTime();
for(var i=0;i<100000;i++){
new Function(); //使用Function构造函数定义的空函数
}
var b = new Date();
var y = b.getTime();
alert(y-x); //2390
JavaScript解释器首先把function语句定义的函数提取出来进行编译,这样每次循环执行该函数时,就不再从头开始重新编译该函数对象了,而Function构造函数定义的函数每次循环时都需要动态编译一次,这样效率就非常低了。
(3)兼容性比较
从兼容角度考虑,使用function语句定义函数不用考虑JavaScript版本问题,所有版本都支持这种方法。而Function构造函数只能在JavaScript 1.1及其以上版本中使用,函数直接量仅在JavaScript 1.2及其以上版本中有效。当然,版本问题现在已经不是问题了。
Function构造函数和函数直接量定义函数方法有点相似,它们都是使用表达式来创建的,而不是通过语句创建的,这样带来了很大的灵活性。对于仅使用一次的函数,非常适合使用表达式的方法来创建。
由于Function构造函数和函数直接量定义函数不需要额外的变量,它们直接在表达式中参与运算,所以节省了资源,克服了使用function语句定义函数占用内存的弊端,也就是说,这些函数运行完毕即被释放而不再占用内存空间。
对于Function构造函数来说,由于定义函数的主体必须以字符串的形式来表示,使用这种方法定义复杂的函数就显得有点笨拙,很容易出现语法错误。但函数直接量的主体使用标准的JavaScript语法,这样看来使用函数直接量是一种比较快捷的方法。
通过function语句定义的函数称为命名式函数、声明式函数或函数常量,而通过匿名方式定义的函数称为引用式函数或函数表达式,而把赋值给变量的匿名函数称为函数对象,把该变量称为函数引用。