首页 » 编写高质量代码:改善JavaScript程序的188个建议 » 编写高质量代码:改善JavaScript程序的188个建议全文在线阅读

《编写高质量代码:改善JavaScript程序的188个建议》第3章 函数式编程

关灯直达底部

JavaScript是一门优美的语言,具有动态性、弱类型,具有C和LISP的双重语法。JavaScript虽然是基于对象编程,但是对象不是第一型的,而函数是第一型的。

函数式编程思想的源头可以追溯到20世纪30年代阿朗佐·丘奇进行的一项关于问题的可计算性的研究,也就是后来的lambda演算。lambda演算的本质就是一切皆函数,函数可以作为另外一个函数的输出或输入,一系列的函数使用最终会形成一个表达式链,通过这个表达式链可以最终求得一个值,而这个过程即为计算的本质。在函数式编程中,会发现代码中存在大量的连续运算。

函数式编程已经在实际应用中发挥了巨大作用,更有越来越多的语言不断地加入对诸如闭包、匿名函数等的支持,从某种程度上来讲,函数式编程正在逐步同化命令式编程。

建议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。

(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语句定义的函数称为命名式函数、声明式函数或函数常量,而通过匿名方式定义的函数称为引用式函数或函数表达式,而把赋值给变量的匿名函数称为函数对象,把该变量称为函数引用。