导航

JavaScript面向对象程序设计1(转载)

Posted on 2009-07-21 11:34  鸡尾虾的壳  阅读(270)  评论(0编辑  收藏  举报
From:豆子空间,http://jbean.javaeye.com/blog/404032

1. 基于对象还是面向对象?

面向对象技术是现代软件开发中的重要技术之一。面向对象变成的好处毋庸置疑,现在的主流语言如Java、C++都是面向对象的。现在的面向对象理论更多的是使用Java或C++进行描述,究其根源,在于这些语言都是传统的面向对象语言,具有面向对象理论所指明的一切特性:类、封装、继承、多态等等。

相比而言,一些动态语言如JavaSript就显得不那么面向对象——至少,在JavaScript中并没有类class这一关键字。但是,在JavaScript中并不是没有类的概念。于是有人说,JavaScript是基于对象的语言,而不是面向对象的语言。

面向对象的语言具有三个特性:封装、继承和多态,三者缺一不可;基于对象的语言通常仅仅是使用对象,其实现的是封装,并没有提供后两种特性。的确,从语法上来说,JavaScript并没有特定的语法或者在语言级别上来实现继承和多态。但是,这并不妨碍我们使用这些特性。这是因为,JavaScript是一种灵活的语言,它是相当的灵活,以至于这些并没有提供的东西,更确切的说,是没有明确的表明的东西,都是可以实现和使用的!那么,你还能说JavaScript是基于对象而不是面向对象的吗?

面向对象也是一种思想,任何语言,包括C语言,同样可以使用面向对象的思想去解决现实生活中的各种问题。到底是基于对象还是面向对象,这些概念让计算机哲学家门去争论吧——相信他们的争论最终也会和先有鸡还是先有蛋的问题一样的结果——我们所要做的,是要使用这种语言提供的机制去解决我们的问题。

2. 为什么要有JavaScript的面向对象编程?

这个问题很严肃——这取决你问题的规模和应用的范围。就像JavaEE和PHP一样:PHP能实现的东西,JavaEE都能实现,那么,为什么还要有PHP?因为JavaEE太复杂了,对于一些简单的系统,根本没有必要是使用它,也就是所谓的“杀鸡焉用牛刀”。

JavaScript主要应用于Web开发中。在传统的Web开发模式中,JavaScript起到的是一些点缀的作用,只完成很有限的功能,例如表单验证等。于是,JavaScript多被当做一种过程性语言使用,很难完成复杂的功能。而今天Web2.0的时代,Ajax大行其道,很多复杂的脚本成为其必须的组成部分。在Ajax应用中利用JavaScript面向对象编程风格,能够使逻辑更加清晰,也更有利于问题的解决。

如果你想用JavaScript编写一个库,比如ExtJS或者YUI,很难想象你的类库不使用面向对象的编程风格——否则的话,无论是对你还是对使用者的智力都将是一个前所未有的考验!或许,自从面向对象思想提出之后,已经很难有类库不使用面向对象的方式实现了,即便是C语言的库诸如gtk+,也是用C语言将面向对象的思想表现的天衣无缝。面向对象的思想对于大型程序的编写和使用具有不可替代的作用。

本系列文章将试图向读者阐述JavaScript的面向对象程序设计。尽管JavaScript中具有很多浏览器相关的概念,如document等内置对象,但是本系列将不涉及这些问题,并且将假设读者已经有JavaScript基础的语法知识等。本系列文章不会从头开始讲述JavaScript的语法,仅仅从纯粹的面向对象角度审视JavaScript,或许,你将会看到一个教程:面向对象程序设计——JavaScript语言描述。这才是本系列文章的目的。


或许你会奇怪,面向对象的程序设计为什么从数组开始讲起?这是因为……其间的种种关系吧……嘿嘿,这里先卖个关子,先来看看我们熟悉的数组在JavaScript里面是什么样子的。
 
1. 创建数组
 
在JavaScript中有很多创建数组的方法。比如使用Array函数。不过这不是现在我们要讲述的。现在我们使用简单的方括号“[]”的办法来创建数组。
 
Js代码 复制代码
  1. var objAyyar = []; // 1    
  2. var objAyyar = [2]; // 2    
  3. var objAyyar = ["a""b""c"]; // 3    
  4. var objAyyar = [new Date(), 123, "abc"]; // 4  
 
这里有四个创建数组的语句。下面来一一解释一下:
 
第一句,创建一个空的数组;
 
第二句,创建一个数组,数组元素只有一个2;
 
第三句,创建一个数组,数组的元素分别初始化为"a", "b", "c";
 
第四句,创建一个数组,其中第一个元素为一个Date类型的对象,第二个元素是数字123,第三个元素是字符串"abc"。
 
回顾一下,在Java或者C++语言中,数组是具有相同的数据类型的元素的集合。比如使用Java语言的下面语句
 
Java代码 复制代码
  1. int[] array = new int[10];  
 
将创建一个能放入10个int类型的元素的数组。数组和其他类型的集合的一个很大的区别是,数组里面只能存放相同数据类型的元素(使用泛型的集合除外)。但是,像上面的第四句,JavaScript的数组怎么能存放不同类型的元素呢?这是因为,JavaScript是弱类型的语言,没有很大的数据类型的差别,所以数组的元素可以放入不同的类型。
 
2. 操作数组
 
数组是元素的有序集合。数组中的元素是有序的,这就可以通过下标访问到数组中的每个元素。而且,JavaScript的数组相当的灵活。当你习惯了Java或者C++的数组之后,或许并不习惯JavaScript的数组。在一定程度上,这种数组可以称为一种动态数组。看这样一段代码:
 
Js代码 复制代码
  1. var arr = [1, 2, 3, 4, 5];       
  2. alert(arr.length); // 数组长度为5    
  3. alert(arr[3]); // arr[3] = 4    
  4. arr[9] = 10;    // 改变了数组的长度为10    
  5. alert(arr[7]);    
  6. alert(arr.length);  
  
首先创建一个数组arr,可以看到它的长度是5,arr[3]是4。这些都是很常见的。那么第三句,arr[9] = 10;就有点意思了——在Java中,这句操作将导致数组越界的异常,在C++中,这种操作是极其危险的。但是在JavaScript中,这样的操作是正常的——你可以动态的改变数组的大小!虽然你在创建数组时并没有这么大的长度,但是,你可以在创建之后指定它!这时的arr.length已经自动的变成10了。那么,arr[7]又会是什么呢?经过运行代码我们会看到,arr[7]是undefined。也就是说,虽然arr[9]有了值,但是其中从arr[5]到arr[8]这几个元素都是未定义的,也就是undefined。如果你问JavaScript怎么不给个初始值?唉,饶了它吧!JavaScript并不知道你想要它初始化成什么值啊!万一错了呢?干脆还是别了吧……
 
Js代码 复制代码
  1. var arr = [1, 2, 3, 4, 5];            
  2. alert(arr.length); // 数组长度为5    
  3. delete arr[3]; // 删掉第4个元素    
  4. alert(arr.length); // 长度不变    
  5. alert(arr[3]); // arr[3] = undefined    
  6. arr.length = 4; // 缩短长度    
  7. alert(arr[4]);    
  8. arr.length = 10; // 增加长度    
  9. alert(arr[6]);  
  
上面的代码也很有意思:使用delete操作符可以删除任意一个数组元素,但是长度并不改变。
 
Java的数组也有一个length属性,用来显示数组的长度。JavaScript的数组也有这个属性。但是,和Java不同的是,后者的length属性并不是只读的!你可以任意的设置数组的length属性的值,无论是扩大还是缩小!只是如上面的代码所示,改变了length之后,越界的元素或者以前没有定义的元素都将成为undefined。也就是说,当length大于原始长度时,从原长度到length - 1的元素都将成为undefined;当length小于原始长度时,从length到原长度 - 1的元素也都会清除设置为undefined。
 
3. 非数字的下标?
 
如果动态的length属性还不够灵活的话,那么,JavaScript的数组还有另外的能力。
 
你见到过用字符串做数组下标的吗?Java行吗?C++行吗?JavaScript就行!看看下面的语句:
 
Js代码 复制代码
  1. var arr = [1, 2, 3];    
  2. alert(arr[1] == arr["1"]);    
  3. arr["js"] = 4;    
  4. alert(arr["js"]);  
  
上面的语句看到,arr[1]和arr["1"]实际是一样的效果!这是怎么回事呢?我们用下面的语句验证一下:
 
Js代码 复制代码
  1. alert(1 == "1"); // true    
  2. alert(1 === "1"); // false  
  
由于JavaScript是弱类型语言,所以在使用变量的时候,JavaScript会尽可能的将它转换成所需要的类型。比如数组下面需要数字,那么提供一个字符串,将会试图把字符串转换成数字。这里的"1"就成功的转换成了数字1,于是这个语句就成立了。这就是使用 == 操作符返回true的原因。而 === 操作符不允许这样的类型转换,所以会返回false。
 
那么,这个arr["js"]怎么也能成立呢?这就不是上面的问题了。也就是说,JavaScript实际是允许将字符串作为数字下标的。这在JavaScript中是完全合法的。

1. 对象
 
对象是面向对象程序设计的基础概念之一,只需看看这个名字就已经知道了。在我们熟悉的面向对象语言中,比如Java或者C++,都有着类似的对象定义方法。比如,我们想定义一个类,名字叫Person,有两个属性:name和age,另外有一个方法,将显示出这个Person对象的名字和年龄,那么我们可以用下面的代码实现:
 
Java代码 复制代码
  1. public class Person {       
  2.         private String name;       
  3.         private int age;       
  4.   
  5.         public String getName() {       
  6.                 return name;       
  7.         }       
  8.   
  9.         public void setName(String name) {       
  10.                 this.name = name;       
  11.         }       
  12.   
  13.         public int getAge() {       
  14.                 return age;       
  15.         }       
  16.   
  17.         public void setAge(int age) {       
  18.                 this.age = age;       
  19.         }       
  20.   
  21.         public void introduction() {       
  22.                 System.out.println("My name is " + this.name + ", my age is " + this.age);       
  23.         }    
  24.   
  25.         public static void main(String[] args) {    
  26.                 Person p = new Person();    
  27.                 p.setName("Tom");    
  28.                 p.setAge(20);    
  29.                 p.introduction();    
  30.         }    
  31. }  
  
C++的实现也是类似的,这里不再赘述。
 
我们先来看一下这个类的定义:首先声明属性,然后定义属性的getter和setter方法,用来外界访问私有变量,最后定义了它自己的方法。这是一个比较常见的定义方式,以至于以后的很多语言,比如C#,都采用这种定义。
 
那么,什么是对象呢?对象不过是具有特定属性和方法的集合。虽然这并不是一个严格的定义,但是将属性和它的名字(不妨我们把它的方法也看作是它的属性,这并没有什么不同)放在一起,形成一个集合,这就是对象。也就是说,简单来看,对象就是这种具有“键-值”对的形式。
 
2. JavaScript的对象
 
“键-值”对的形式……这个样子看上去是不是有些面熟?Bingo!对了!这不就是数组的形式吗?嗯,恭喜你想到这一点!的确,在JavaScript中,对象的定义就是像数组的定义。下面,我们在JavaScript中对这个Person进行一下定义:
 
Js代码 复制代码
  1. var Person = {       
  2.         "name""Tom",       
  3.         "age": 20,       
  4.         "introduction"function() {       
  5.                 alert("My name is " + this.name + ", my age is " + this.age);       
  6.         }       
  7. };       
  8. Person.introduction();  
  
来看一下这段代码。看上去很像数组的定义,只不过数组一般使用数字类型作为下标,而这里我们使用的是字符串。回想一下,其实在JavaScript中,字符串也是可以作为数组下标的,不是吗?好了,这里我们声明了一个对象Person,它有一个name,还有一个age,而且还有一个方法显示出这两个属性。
 
在JavaScript中,对象就是“键-值”对的形式,具体来说是"string-as-key": object-as-value的形式。也就是说,这个键必须是string类型的,而值可以是任何类型的。那么,方法呢?其实,JavaScript中的function也是一个类型,这个在后面会有描述的,这里仅仅先知道就可以了。这种数学上成为二元组的样式很常见,数组就是这样的,只不过数组的键必须是int。同样,JavaScript的对象也是一个特殊的二元组,只不过键是string类型的。这是不是就像是一种散列?或者说是哈希表?就是这个样子!
 
如果说你觉得每个属性名都要加一个引号觉得很别扭,那么你大可不加!像下面的语句,JavaScript完全认为你的正确的:
 
Js代码 复制代码
  1. var Person = {    
  2.         name: "Tom",    
  3.         age: 20,    
  4.         introduction: function() {    
  5.                 alert("My name is " + this.name + ", my age is " + this.age);    
  6.         }    
  7. }    
  8. Person.introduction();  
  
我比较习惯于这种写法,看上去和Java等语言差不多。
 
3. 属性的使用
 
JavaScript中属性的使用或许比较特别。看下面试图使用Person的name属性的四个语句,看上去都差不多,实际上也确实如此:
 
Js代码 复制代码
  1. alert(Person.name); // Tom   
  2. // alert(Person."name");    
  3. alert(Person["name"]); // Tom   
  4. alert(Person[name]); // undefined  
  
除去注释掉的一句,其他的三个语句都能够通过解释(由于JavaScript是解释型语言,不是编译型的,因此这里不说是编译),但是只有1、3句能够取出name属性!第一句和Java没有什么区别,后面的两个显得比较特别,第三句看上去像什么?对了!数组元素的访问!这进一步验证了JavaScript中的数组和对象“本是同根生”。那么,第四句呢?当然是返回undefined!因为数组下标必须是数字或者字符串嘛!
 
熟悉Java的话或许会对introduction函数有些疑问。Java程序员不会时时刻刻把this加上,除非哪天心血来潮,才会加上这个this。但是,如果你想在JavaScript中偷懒,去掉this,结果只会是报错。这是怎么回事呢?简单来说,在这里的this关键字是必不可少的!这是JavaScript与其他语言的不同。具体为什么,会在以后的文章中说明,现在只是知道就好了——不要在这里偷懒哦~~
 
4. 更多属性的操作
 
现在对JavaScript对象属性的认识应该在这样一点上:JavaScript的对象就是一个二元组,或者说就是一个散列或哈希表。如果能明白这一点,就不会对下面的操作有所奇怪了:
 
Js代码 复制代码
  1. var Person = {}; // 创建一个空对象    
  2. Person.name = "Tom"// 添加一个属性name,并赋值为Tom    
  3. Person["age"] = 20; // 用另外的办法新增属性    
  4. Person.introduction = function () {    
  5.         alert("My name is " + this.name + ", my age is " + this.age);    
  6. };    
  7. Person.introduction();    
  8. for(var field in Person) { // 使用foreach循环列出对象中所有属性    
  9.         alert("field name: " + field + "; value: " + Person[field]);    
  10. }    
  11. delete Person.name; // 删除name属性    
  12. Person.introduction();    
  13. alert(name in Person); // 使用in操作符判断属性是否存在  
  
5. 对象的constructor属性
 
在JavaScript中,每个对象都有一个constructor属性。这个constructor属性用来记录对象初始化时的构造函数名字。例如:
 
Js代码 复制代码
  1. var date = new Date();    
  2. alert(date.constructor);    
  3. alert(date.constructor == "Date"); // false    
  4. alert(date.constructor == Date); // true  
  
嗯,这个Date是JavaScript的内置对象。这里的代码看上去很平常。不过,如果你要使用自己写的对象,比如前面的Person,就会发现它的constructor对象怎么是Object?这里有两个问题:第一,我们并没有给Person constructor属性,它怎么会有的?第二,这个constructor属性怎么是object,而不是我们的Person呢?
 
对于第一个问题,很明显,是JavaScript给我们加上的。事实上,每个JavaScript对象都会有这样一个属性。那么,它的值又怎么是Object呢?这个问题,在我们说道new这个运算符的时候会给大家说明的。这里请大家注意,本文中的对象其实是指的单独的使用new之后得到的对象。也就是说,那个constructor属性是在new运算符的时候获得的。这就涉及到构造函数了——不过这不是本文的重点,以后再说吧~~ :-)

在很多语言中,函数(Java里面成为方法)和对象时截然不同的两种东西。函数被定义为对象的动作,或者是全局的(像在C++中的main函数一样)。但是在JavaScript中,函数和对象的界限却显得不那么明显。

 

1. 函数的定义

 

JavaScript中有很多种定义函数的方法:

 

Js代码 复制代码
  1. function hello() { alert("Hello!"); }   
  2. var hello1 = function() { alert("Hello!"); };   
  3. var hello2 = new Function("""alert('Hello!');");   
  4. hello();   
  5. hello1();   
  6. hello2();  

 

上面给出了三种JavaScript的函数定义语句。第一句是常见的定义,看上去和Java等语言没有太大的不同。这句是定义了一个具名函数,按照上面的例子,这里的函数定义名字为hello。第二句是将一个匿名函数定义好后赋值给一个变量,于是通过这个变量就可以引用这个匿名函数。这两句看上去效果差不多,但是它们是不一样的:第一句定义的是一个具名函数,第二句定义的是一个匿名函数——尽管你可以通过这个变量引用到这个匿名函数,但实际上它还是匿名的。它们的区别可以由下面的看出:

 

Js代码 复制代码
  1. hello();   
  2. hello1(); // error   
  3. function hello() { alert("Hello!"); }   
  4. var hello1 = function() { alert("Hello!"); };  

 

具名函数的作用范围是全局的:你可以在定义之前使用这个函数。但是匿名函数的定义是后向的,像C/C++一样,必须在定义之后才能使用。这就是为什么hello可以使用,但是hello1就会有错误。然后试想一下这是为什么呢?JavaScript的解释过程和HTML一样是从上到下的。所以,这里的匿名函数就相当于是一个变量的定义,因此在JavaScript解释器解释执行时并不知道这个变量的定义,因此发生错误。但是,对于函数的定义则是扫描全局。

 

第三个语句就很有意思了。它创建了一个Function类的对象。这个构造函数(姑且这么叫吧)具有两个参数,第一个是函数的参数,第二个是函数体。具体来说,下面的两个函数定义是等价的:

 

Js代码 复制代码
  1. function sayHelloTo(name) {   
  2.     alert("Hello, " + name);   
  3. }   
  4. var sayHelloTo1 = new Function("name""alert('Hello, ' + name)");  

 

这种使用Function进行定义的方式并不常见,但是这个语句显示的特性却很有趣:它意味着,你可以使用这种构造函数在运行时动态的构造函数!这是一般的语言没有的特性。

 

2. 函数的参数

 

JavaScript的函数也是相当的灵活,不仅是它的定义方式多种多样,甚至它的参数都有“奇怪”的行为。由于JavaScript是弱类型的语言,因此,它不能对你的函数参数类型做检测,甚至不能保证你传入的参数个数是否和函数定义一致。这就需要有一些特殊的检测。

 

Js代码 复制代码
  1. function sum2(a, b) {   
  2.     alert(a + b);   
  3. }   
  4. sum2(1); // NaN   
  5. sum2(1, 2); // 3   
  6. sum2(1, 3, 5); // 4  

 

看这个例子,仅仅接受两个参数的函数,在调用时可以有任意个参数!但是,它仅取用符合条件的个数,在这里也就是前两个参数。所以,当你传入一个参数时,JavaScript试图将两个数字加起来,结果第二个参数不存在,因此返回值是NaN。第三种情况,实参个数多于形参个数,此时JavaScript只取前两个参数相加。

 

尽管很不正式,但是可以说,JavaScript的函数参数是不定参数,也就是说,你可以传入任意的参数值。使用JavaScript函数内置的arguments就可以遍历所有传入的参数。比如下面的代码:

 

Js代码 复制代码
  1. function sum() {   
  2.     var total = 0;   
  3.     for(var i = 0; i < arguments.length; i++) {   
  4.         total += arguments[i];   
  5.     }   
  6.     alert(total);   
  7. }   
  8. sum(1, 2);   
  9. sum(1, 2, 3);  

 

arguments的行为很像数组,但它并不是数组。可以使用typeof操作符看一下,也可以调用它的constructor属性。

 

这里有一点需要说明,arguments有个callee属性,可以调用arguments自身所在的函数。也就是说,可以通过这个属性递归调用函数自身。所以,即使是匿名函数,也可以实现递归调用。如:

 

Js代码 复制代码
  1. function sum(n) {   
  2.     if(n <= 1) {   
  3.         return 1;   
  4.     }   
  5.     return n + arguments.callee(n - 1); // 递归调用自身   
  6. }   
  7. alert(sum(100));  

 

我觉得大家都会知道这个著名问题的答案的。

 

3. 函数也是对象

 

回想一下上面的第三个语句,它已经强烈暗示了,函数其实也是对象!那么,作为一个对象,函数应该具有对象的一切特性:添加属性、删除属性、作为返回值等等。是的!JavaScript的函数就是这么样的!

 

Js代码 复制代码
  1. function hello() {   
  2.     alert("Hello!");   
  3. }   
  4. hello.name = "Tom"// 添加属性   
  5. alert(hello["name"]);   
  6. delete hello.name; // 删除属性   
  7. alert(hello.name);   
  8. // 赋值给变量   
  9. var hello1 = function() { alert("hello1"); };   
  10. hello1();   
  11. // 作为数组元素   
  12. function show(x) { alert(x); }   
  13. var arr = [show];   
  14. arr[0](5);   
  15. // 作为函数的参数   
  16. function callFunc(func) {   
  17.     func();   
  18. }   
  19. callFunc(function() {   
  20.     alert("Inner Function.");   
  21. });   
  22. // 作为函数的返回值   
  23. function show() {   
  24.     return function(n) {   
  25.         alert("number is " + n);   
  26.     };   
  27. }   
  28. show()(10);  

 瞧!凡是对象可以做到的,函数统统都能做到!JavaScript中的函数就是对象!

现在我们已经从数组,逐渐开始到对象和函数。这些都是基本概念,后面,我们将对JavaScript的面向对象特性做进一步的介绍。