yunshichen

我相信人生是值得活的,尽管人在一生中必须遭受痛苦,卑劣,残酷,不幸和死亡的折磨,我依然深信如此.但我认为人生不一定要有意义,只是对一些人而言,他们可以使人生有意义. ---J 赫胥黎

Advanced JavaScript 2: OO Design


一 JavaScript的类代码

这是个简单但功能齐全的类,这个例子将作为继续讨论的基础,请注意观察:

<html>
<body>
<script>
    
var Employee=function(name,title){
            
//public properties.
        this.name=name;
        
this.title=title;        
      
          
//Every employee will have a grade and it won't be exposed.
          //If you don't ask hr, they don't tell you.       
        this._grade;
        
        
this.calculateGrade(this);          
    };
    Employee.prototype.calculateGrade
=function(self){
        
switch(this.title){
          
case "Engineer":
              
this._grade=10;
              
break;
          
case "Manager":
              
this._grade=20;
              
break;
          
default:
              
this._grade=1;
      }
    }
    Employee.prototype.askMyGrade
=function(){
        
return this._grade;
    };
    Employee.prototype.sayHello
=function(){
        
var str="Hi,my name is "+this.name+" and I'm a "+this.title+ ".\n";
        str
+="Nice to meet you, "+ this.whichOne(this)+" !";
        alert(str);
    }
    
//whichOne is an interface.
    Employee.prototype.whichOne=function(self){
        
return "Boss";
    }
    
    
var p1 =new Employee("Diego","Engineer");
    p1.sayHello();
    
    
var p2 =new Employee("Cage","Manager");
    p2.whichOne
=function(self){
        
return "Diego";
    }
    p2.sayHello();    
       
</script>
</body>
</html>

Java的OO概念在JavaScript中是如何实现的呢?下面是简单的概念说明:

class(类): JS没有类关键字而用 function 来实现类. 所以任何function都是类.
constructor(构造函数): function 就是构造函数.
instantiate(实例化): 关键字 new 和构造函数在一起创建出对象的过程.
properties of instance(实例属性): 在function内用this关键字去定义.
method of class(类的方法): 用prototype去定义.注意,JS里类方法并不在类里定义.因为方法如果放在类里,要么是私有方法实例无法访问,要么是实例方法.每个实例都有这个方法,浪费内存空间.
callback(回调方法): 用prototype去定义,在实例里被覆盖的方法.JS通过这种巧妙的方式来实现回调功能.
interface(接口): 如果按照严格的interface定义,JS里没有相应概念.一些JS框架如YUI和ExtJS提到的接口都是指callback


二 封装和可见域

****为简化讨论,本节不涉及closure知识.

如上一章所讨论的,JS没有包的概念, 通过精巧的技术模拟实现包,下面继续讨论JS的封装性

<html>
<body>
<script>
    
var Employee=function(name){
            
//public properties.
        this.name=name;
        
this._salary=500;
        
var age=50;
        
var askAge=function(){
            
return a;
        }     
    };
    Employee.prototype.getSalary
=function(){
        
return this._salary;
    }
    
var p =new Employee("Diego");
    alert(p.getSalary());
    alert(p.age);//undefined
    alert(p.askAge());//no this method
</script>
</body>
</html>

程序运行到alert(p.askAge())会报错: p实例并没有askAge方法.

在定义JavaScript的类时,属性/方法要么用var定义,要么用this定义.用var定义的属性/方法只能在该function内部使用. 实例和类方法(用prototype定义的方法)都无法访问该字段. 也就是说,类方法能访问的字段都是实例字段,所有实例字段都是public的.如果该实例字段不希望被直接访问,那么只能用一些约定俗成的方法来达到目的. YUI的所有"私有"字段都用_开头,如上面例子的 this._salary

不考虑closure(闭包)的情况下,JavaScript的可见性可简单总结为:

private(私有的): 在构造函数里用var定义的变量/方法,只能在构造函数里访问.
public(公开的): 用this定义的实例变量/方法.
static(静态的): 由于构造函数function本身也是对象,所以给该对象加上属性就是static属性,可以通过 FuncName.prop 来访问. 但在实际的JS开发中,static概念很少用到.




三 继承,覆盖和多态

先看看以下例子

<html>
<body>
<script>
    
       
//Use Object constructor to instantiate an instance.
      var o1 = new Object();
      alert(
"o1's constructor is: \n"+o1.constructor);
      alert(
"o1's toString: "+o1.toString());
      
//Rewrite toString method of Object constructor.
        Object.prototype.toString=function(){
            
return "I'm toString()";
        }
      alert(
"o1's toString: "+o1.toString());
            
      
//Define an Employee constructor.
    var Employee=function(name){            
        
this.name=name;
    };
    
var p =new Employee("Diego");    
      alert(
"p's constructor is: \n"+p.constructor);
      alert(
"p's toString: "+p.toString());      
    
</script>
</body>
</html>


这个例子表明JavaScript存在着继承机制:所有函数都继承于Object类,共享Object的toString方法.

以上面的Employee函数为例,JS的继承原理如下:
  • 对于所有函数,JS都将此函数的prototype对象设置为超类的一个实例,即Employee.prototype=new Object();
  • 当调用Employee实例的toString函数时,JS会先寻找p实例是否有toString方法,然后搜索prototype对象,由于Empoyee的prototype其实是Object的一个实例,所以JS继续搜索Object.prototype,最后找到toString方法.
  • 由于调用方法时,该方法的寻找可一直上溯到Object的prototype对象,所以这种层次关系也成为prototype chain(原型链).
据此,当我们要实现JS的继承时,过程描述如下:
  • 将子类的prototype对象设为超类的一个实例.
  • 如果子类方法要覆盖父类的同名方法,在子类的prototype定义一个同名方法即可.
代码如下:

<html>
<body>
<script>                
      
//Employee is super class
    var Employee=function(name){            
        
this.name=name;
    }; 
    
    Employee.prototype.getName
=function(){
        
return this.name;
    }
    Employee.prototype.toString
=function(){
        
return "Employee:{name:"+this.name+"}";
    }
    
    
var Engineer=function(name,majority){
        Employee.call(
this,name);
        
this.majority=majority;
    }
    
    
//Set prototype to an instance of super class.
    Engineer.prototype=new Employee();
    
    Engineer.prototype.getMajority
=function(){
        
return this.majority;
    }
    Engineer.prototype.toString
=function(){
        
return "Engineer:{name:"+this.name+",majority:"+this.majority+"}";
    }
    
    
var o1=new Employee("Chen");
    alert(
"Employee o1's toString:\n"+o1.toString());
    
    
var o2=new Engineer("Zhang","Computer Science");
    alert(
"Engineer o2's name:\n"+o2.getName());
    alert(
"Engineer o2's majority:\n"+o2.getMajority());
    alert(
"Engineer o2's toString:\n"+o2.toString());    
    
</script>
</body>
</html>

JavaScript并没有super关键字,但别忘了JS中的类其实都是函数.如果在子类想调用超类的方法来初始化部分属性,可以用function_name.call(this,....)来达到目的,如上面代码的Employee.call(this,name);

在JS中,忘了多态这个概念吧.JS是弱类型语言,不需要类型转换.所以根本不需要多态这个特性.

别忘了JS的function可以作为其他对象的属性,所以可以抛开类的思想来实现代码的共享.如果某个函数需要另一个函数的方法,干嘛要继承?复制过来不就完了.:-)

<html>
<body>
<script>                
    
        
function copyMethods(fromClass,toClass){
            
var from=fromClass.prototype;
            
var to=toClass.prototype;
            
for(m in from){
                
if(typeof from[m]!="function")continue;
                to[m]
=from[m];
            }
        }    
      
//Employee is super class
    var Employee=function(name){            
        
this.name=name;
    }; 
    
    Employee.prototype.getName
=function(){
        
return this.name;
    }
    Employee.prototype.toString
=function(){
        
return "Employee:{name:"+this.name+"}";
    }    
    
var Engineer=function(name,majority){
        Employee.call(
this,name);
        
this.majority=majority;
    }
    copyMethods(Employee,Engineer);
        
    Engineer.prototype.getMajority
=function(){
        
return this.majority;
    }
    Engineer.prototype.toString
=function(){
        
return "Engineer:{name:"+this.name+",majority:"+this.majority+"}";
    }
    
    
var o1=new Employee("Chen");
    alert(
"Employee o1's toString:\n"+o1.toString());
    
    
var o2=new Engineer("Zhang","Computer Science");
    alert(
"Engineer o2's name:\n"+o2.getName());
    alert(
"Engineer o2's majority:\n"+o2.getMajority());
    alert(
"Engineer o2's toString:\n"+o2.toString());    
    
</script>
</body>
</html>

关于JavaScript的类和继承的题外话:

JavaScript只有模拟的而没有明确的类和继承机制. 但是通过应用prototype可以用多种方式灵活的实现功能等同于类和继承的代码.另一方面,这种灵活性也给程序员带来了一定程度的困惑,搜索JavaScript 类继承, 你可以看到网上有各式各样五花八门的实现方法.那么以那一种为准呢?很难断定.

我个人信奉Python哲学:代码只有一种正确的写法. 所以本节我介绍了标准的编写类和继承代码的方式,而在下一章会以流行的ExtJS框架来进一步介绍类和继承.以此来作为个人写JS的标准.如果你想开阔视野多看看别人的实现方式,可参考Douglas的系列文章



to be continued....



posted on 2009-06-19 16:47 Chenyunshi 阅读(465) 评论(3)  编辑 收藏 引用 所属分类: JavaScript

评论

# That's not just the best aesnwr. It's the bestest answer! 2011-05-22 21:55 Grizzly

That's not just the best aesnwr. It's the bestest answer!  回复  更多评论   

只有注册用户登录后才能发表评论。
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

导航

统计

常用链接

留言簿(7)

随笔分类

随笔档案

文章分类

相册

搜索

最新评论

阅读排行榜

评论排行榜