函数型 VS 面向对象型的javascript程序设计

原文点击这里

灵活的js语言可以轻易的就完成截然相反的两种编程模式–函数型程序设计和面向对象的程序设计。

js原生的支持将函数当做变量来处理。你可以将一个函数赋值给一个变量,然后把他传给其他的变量。抽象的说,我们可以认为js的函数是一个特殊的变量:只不过他的形式是“函数”。

js原生的也提供对象。在js中,对象可以认为是一系列平面化的属性值和方法。他们看起来像是一个数据字典,而不像你在其他诸如java、c++、c#中看到的对象那样。在经典的面向对象编程中,类表示可以通过new操作符来生成一个实例的模板。但是在js中,没有可以当做蓝本来生成对象的。在js中,一个对象的蓝本更像是一个数据字典。所以在js中,你可以创建对象,然后可以在对象中存储数据。当然,js的对象也提供一定程度的面向对象的特性,比如封装和继承。

js开发越来越热,两种模式的优劣是什么?js对两种模式都支持,但是问题的答案得你自己来弄清楚。它两种都支持,但并不是某一种特别的合适。OOP的js框架和函数型的js都同时存在。

函数型程序设计简介

函数型程序设计中,每个代码段都是在”function”之上建立的,这与传统的OOP建立在”class”之上是不同的。一个函数只是对输入的展现的一种操作。一个函数只是接收一些输入,然后返回一些输出,其他的都是隐藏起来的。

在函数型程序设计中,你通过在一个函数中调用其他的函数来完成程序的输入与输出。这通常都没有可以处理输入、存储数据以及一系列的状态更新的那一层。函数就像一个数值一样,可以作为参数传递给其他函数。

javascript与函数型程序设计

需要明确的一个事实是:js并不是像F#那样真正的函数型程序设计语言,虽然他有一些函数型程序设计模式的特性。利用这些特性,你也可以用js做很好的工作。到今天为止,使用jQuery就是这种模式最广泛的应用了。

匿名函数是函数型程序设计的精华之所在。匿名函数是lambda计算式的另一种形式,一种可以适配经流行的编码风格。

1
2
3
function (x,y){
     return x + y;
}

普通函数和匿名的函数的唯一区别就在于名字。在函数的上下文中,当你只是需要函数的返回值作为参数来使用的时候,并不是非得要给他一个函数名。通过下面这个例子我们来看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 一个用来计算和的函数
var CalcTotal = function(x, y) {
   return x + y;
};
 
// 增加税率
var AddTaxes = function(x) {
  return x * 1.2;
};
 
// 最终的计算函数
var CalcTotalPlusTaxes = function (fnCalcTotal, fnAddTaxes, x, y) {
     return fnAddTaxes(fnCalcTotal(x, y));
};
 
// 执行
var result = CalcTotalPlusTaxes(CalcTotal, AddTaxes, 40, 60);
alert(result);

需要注意的是,你也可以使用匿名函数,而不是非得通过一个中间变量来传递给最终的计算式。

1
2
3
alert(
     (function(x){return x * 1.2})(100);
);

将匿名函数看做一个变量,可以很轻松的将数据和特性合并在一起。这也给你了一个机会去试验一些其他的设计原则。当高一级的模块(无论对象还是函数)需要低一级的模块提供的返回值的时候,就可以将低一级的模块直接当做参数传过去。很多情况下,这会提高函数参数的可读性,并且让代码更优雅一些,当然,也会让你在处理复杂事物的时候更加得心应手。

jQuery和函数型程序设计

jQuery在函数型程序设计方面吸引了人们极大的关注。整个jQuery库都是基于jquery对象或者说$。$是DOM元素的一个封装,DOM元素也可以通过$()表达式传递进去。另外,jQuery支持链式操作,每一个函数运行之后都会返回同一个jquery对象。

jQuery非常高效,因为你可以在主要工作是操作DOM元素的WEB编程环境下最大限度的发挥出函数型程序设计的威力。你也许是jQuery的粉丝,并且觉得这种函数式的操作非常赞。但是,这不是需要完全把这种编程模式到处使用的理由。我们通过下面这个jQuery源码来看个例子:

1
2
3
4
5
6
7
8
jQuery.fn = jQuery.prototype = {
    init: function( selector, context ) { ... },
       size: function() { return this.length; },
       each: function( callback, args ) {
        return jQuery.each( this, callback, args );  },
       ready: function( fn ) { ... }
       :
}

可以看到,jQuery对象有他自己的原型,也有一些自有的方法:比如size和each。更有意思的是,当你写jQuery的插件的时候,你只是给通过添加一个函数扩展了原型方法。至此,所有高效的js编程,都是两种模式混合的结果。当然jQuery中函数型的模式更加明显一些。

javascript中的对象

真正面向对象编程中的对象与javascript中的对象有一个很明显的不同。在面向对象的语言中,类是你要使用的对象的蓝本。在javascript中,你所使用的对象的蓝本是一个数据字典或者一个函数。当你在js中创建了一个对象,你就拥有一个你可以填进去任何数据的空的数据字典。

之前说过,通过一些方法,你可以创建一些自定义的对象或者继承自现有的对象。这只在js中有效。

当你需要通过添加中间的转化来接近真正的面向对象语言的时候,在js中有两种办法:闭包和原型。

在讲述那两种办法之前,我们先来讲讲js中的对象类型和他的用法。

你可以通过new关键字来创建一个空的对象,然后你可以给这个对象添加进去你想要的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var person = new Object();
person.Name = "Dino";
person.LastName = "Esposito";
person.BirthDate = new Date(1979,10,17)
person.getAge = function() {
  var today = new Date();
  var thisDay = today.getDate();
  var thisMonth = today.getMonth();
  var thisYear = today.getFullYear();
  var age = thisYear-this.BirthDate.getFullYear()-1;
  if (thisMonth > this.BirthDate.getMonth())
      age = age +1;
  else
  if (thisMonth == this.BirthDate.getMonth() &&
      thisDay >= this.BirthDate.getDate())
      age = age +1;
  return age;
}

我们现在拥有一个名为person的对象,但是没有Person对象。在js中,所有的原声对象都有一个只读的prototype属性。通过这个属性,你可以用来提供一些函数,这些函数在使用new关键字的时候,新生成的对象可以与原对象共享这些方法。下面有两种方法可以在js中实现面向对象编程。

通过闭包实现面向对象

闭包是编程语言中的一个基本概念。在js中,闭包是指那些拥有同样上下文的变量和方法的一个函数。下面这个例子就是使用闭包来模拟一个Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var Person = function(name, lastname, birthdate){
   this.Name = name;
   this.LastName = lastname;
   this.BirthDate = birthdate;
 
   this.getAge = function() {
      var today = new Date();
      var thisDay = today.getDate();
      var thisMonth = today.getMonth();
      var thisYear = today.getFullYear();
      var age = thisYear-this.BirthDate.getFullYear()-1;
      if (thisMonth > this.BirthDate.getMonth())
          age = age +1;
      else
         if (thisMonth == this.BirthDate.getMonth() &&
             thisDay >= this.BirthDate.getDate())
             age = age +1;
      return age;
   }
}

可以看出,闭包就是父类的构造函数。在闭包模式中,构造器包含类成员的声明,并且这些成员都是封装起来的。另外,成员都是基于实例的,所以会消耗内存。如何使用呢:

1
2
var p = new Person('Rock','ux',new Date());
alert(p.name + 'is' + p.getAge());

闭包模式是完全封装的,仅此而已。

原型模式的面向对象方法

通过js中prototype对象,你可以定义一个类的结构。下面这个例子用来说明怎样避免使用闭包来重写Person类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 构造器
var Person = function(name, lastname, birthdate)
{
    this.initialize(name, lastname, birthdate);
}
 
// 成员
Person.prototype.initialize(name, lastname, birthdate)
{
     this.Name = name;
     this.LastName = lastname;
     this.BirthDate = birthdate;
}
Person.prototype.getAge = function()
{
     var today = new Date();
     var thisDay = today.getDate();
     var thisMonth = today.getMonth();
     var thisYear = today.getFullYear();
     var age = thisYear-this.BirthDate.getFullYear()-1;
     if (thisMonth > this.BirthDate.getMonth())
         age = age +1;
     else
        if (thisMonth == this.BirthDate.getMonth() &&
            thisDay >= this.BirthDate.getDate())
            age = age +1;
     return age;
}

在原型模式下面,构造器和成员之间结构清晰,但是构造函数是必须的。这里并没有私有成员,var关键字可以让其只在闭包中有效。你可以通过定义setter/getter来操作你需要的属性,但是这些属性在类外面也是可以直接访问修改的。你可以通过一些特殊的操作(比如添加前缀)来定义私有变量。只是一种办法而已。

原型模式可以很简单的实现继承:

1
2
3
4
5
Developer = function Developer(name, lastname, birthdate)
{
   this.initialize(name, lastname, birthdate);
}
Developer.prototype = new Person();

需要注意的是,你必须通过使用this关键字来访问原型中的相关成员方法。

闭包还是原型?

在原型模式中,所有的成员都是共享的。所以,相比而言,内存的开销要小一些。除去语法方面的不同,通过原型模式定义的类更加接近经典意义上的面向对象语言。

闭包还是原型的选择还要从性能和浏览器的兼容性来考虑。原型模式的加载速度不错,而且在Firefox中性能也很好。(相比而言,闭包模式在IE下面的加载更快一些。)原型模式对于智能感知支持不错,这样在一些IDE中(比如VS)就能得到很好的工具支持。原型模式下,你不必通过创建一个实例来查看类型信息,闭包模式下就不行。最后,调试的时候,原型模式可以方便的访问私有成员,闭包模式下面得多几步操作才行。

举个例子,微软的ajax库使用的就是原型模式(闭包模式从未被考虑)。

结论

Javascript并不是纯粹的函数型或者面向对象的语言。但是他从两种语言都有借鉴,所以你需要做好一些准备。

如今,Javascript对于编写客户端的网页和移动应用程序来说必不可少。而且现在的脚本编写也不像当初这个语言开始使用的时候了。现在我们经常使用js来做客户端的表单验证等程序。为了让开发进程更加的平稳,我们也需要一些js库。jQuery就是最好的例证。

随着jQuery大放光芒,函数型变成再次在js语言中相对于面向对象得到了更多的关注。最终,选择什么还是在你。你也不必像选择一种信仰似的选择一种开发模式,开发中,两者混合使用是非常常见的事情。

如前所述,如果我是前端开发,我可能会使用jQuery,然后使用一些匿名函数,不去关心什么自定义的对象。如果我是要自己写一个js的库,那么我肯定会选择面向对象开发,或者使用闭包模式或者使用原型模式。

点击查看原文

posted @ 2011-02-26 19:21 BeiYuu 阅读(...) 评论(...) 编辑 收藏