面向对象只是过程化程序设计方法的一个层次,它是目前我们所知的一种比较高级的过程化境界(但不是最高的)
面向对象是一种思想而不是一种固定的套路
 
面向对象的三大特点(封装延展多态)缺一不可。"基于对象"是使用对象,但是不一定支持利用现有的对象模板产生新的对象类型,继而产生新的对象,也就是说“基于对象“不要求拥有继承的特点。“多态”表示为父类类型的子类对象实例,没有了继承的概念也就无从讨论“多态“。“面向对象”和“基于对象”都实现了“封装”的概念,但是面向对象实现了“继承和多态”,而“基于对象”可以不实现这些。
 
21.1.1 类和对象
三种构造对象的方法:
例21.1 对象的三种基本构造法.html
<script>
//第一种构造法:new Object
//通过实例化一个Object来生成对象,构造基本对象直接添加属性
var a = new Object();
a.x = 1,a.y = 2;

//第二种构造法:对象直接量
var b = {x : 1, y : 2};

//第三种构造法:定义类型
function Point(x,y)
{
    this.x = x;
    this.y = y;
}
var p = new Point(1,2);

var a1 = new Object();
var a2 = new Object();
a1.x = 1,a1.y = 2;
a2.x = 3,a2.y = 4, a2.z = 5;
//你既没有办法说明a1、a2是同一种类型,也没有办法说明它们是不同的类型
</script>
javascript是一种弱类型的语言,一方面表现在JavaScript的变量、参数和返回值可以是任意类型,另一方面也体现在,JavaScript可以对对象任意添加属性和方法,这样无形中就淡化了“类型”的概念。
 
 
21.1.2 公有和私有——属性的封装
封装:属性或方法可以被声明为公有或者私有,只有共有的属性或方法才可以被外部环境感知和访问
例21.2 对象的公有和私有特性.html
<script>
function List()
{
    var m_elements = []; //私有成员,在对象外无法访问

    m_elements = Array.apply(m_elements,arguments);
    
//公有属性,可以通过“.”运算符或下标来访问
this.length = {
         valueOf : function(){
  return m_elements.length;    
         },
         toString : function(){
                return m_elements.length;
         }
}
    
this.toString = function(){
     return m_elements.toString();    
}
    
this.add = function(){
     m_elements.push.apply(m_elements,arguments);
}
}

var alist = new List(1,2,3);
alert("alist: " + alist);
alert("alist.length: " + alist.length);
alist.add(4,5,6);
alert("alist: " + alist);
alert("alist.lenght: " + alist.length);
</script>
JavaScript中,函数是绝对的“第一型”,JavaScript的对象和闭包都是通过函数实现的。
 
对象的getter是一种特殊的属性,它形式上像是变量或者对象属性,但是它的值随着对象的某些参数改变而变化。在不支持getter的语言中,用get<Name>方法来代替getter,其中<Name>是getter的实际名字,这种用法产生的效果和getter等价,但是形式上不够简洁。ECMAScript v3不支持getter,可以构造带有自定义valueOf和toString方法的对象来巧妙地模拟getter。
使用toString和valueOf模拟getter方法.html
<script>
//使用getName()方式
function Foo(a,b)
{
    this.a = a;
    this.b = b;
    this.getSum = function()
    {
        return a + b;
    }
}
alert((new Foo(1,2)).getSum()); //得到3
//模拟getter
function Foo1(a,b)
{
    this.a = a;
    this.b = b;
    this.sum = {
        valueOf:function(){return a+b},
        toString:function(){return a+b}
    }
}
alert((new Foo1(1,2)).sum); //同样得到3
</script>

对象的setter是另一个相对应的属性,它的作用是通过类似赋值的方式改变对象的某些参数或者状态。要实现setter的效果,只有通过定义set<Name>方法来实现。
21.1.3 属性和方法的类型
JavaScript里,对象的属性和方法支持4种不同的类型,第一种类型是私有类型,它的特点是对外界完全不具备被访问性,要访问它们,只有通过特定的getter和setter。第二种类型是动态的公有类型,它的特点是外界可以访问,而且每个对象实例持有一个副本,它们之间不会相互影响。第三种类型是静态的公有类型,或者通常叫做原型属性,它的特点是每个对象实例共享唯一副本,对它的改写会相互影响。第四种类型是类属性,它的特点是作为类型的属性而不是对象类型的属性,在没有构造对象时也能够访问。
例21.3 类型的四种属性.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charseet=utf-8">
</head>
<body>
<h1 id="output"></h1/>
<script language="javascript" type="text/javascript">
<!--
function dwn(s){
     document.write(s + "<br/>");
}

function myClass(){
     var p = 100; //private property;私有属性
     this.x = 10; //dynamic public property 动态公有属性
}
myClass.prototype.y = 20; //static public property or prototype property原型属性
myClass.z = 30; //static property //静态属性
    
var a = new myClass();
dwn("私有属性: " + a.p); //undefined 私有属性对象无法访问到
dwn("动态公有属性: " + a.x); //10 公有属性
dwn("静态公有属性: " + a.y); //20 静态公有属性
a.x = 20;
a.y = 40;
dwn("动态公有属性可以通过对象来改变:" + a.x); //20
dwn("动态公有属性y覆盖了原型属性y:" + a.y);    //40
delete(a.x);
delete(a.y);
dwn("删除动态公有属性之后: " + a.x); //undefined 动态公有属性x被删除后不存在
dwn("动态公有属性y被删除后还原为原型属性y :" + a.y) //20
dwn("类属性无法通过对象访问:" + a.z); //undefined
dwn(myClass.z);        //30 类属性应该通过类访问

-->
</script>
</body>
</html>
例21.3 类型的四种方法.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charseet=utf-8">
</head>
<body>
<h1 id="output"></h1/>
<script language="javascript" type="text/javascript">
<!--
function dwn(s){
     document.write(s + "<br/>");
}

function myClass(){
     var p = function(s){ dwn(s);}; //private method;私有方法
     this.x = dwn; //dynamic public method; 动态公有方法
     this.w = function(){ p("私有方法"); };
}
myClass.prototype.y = dwn; //static public method or prototype method原型方法
myClass.z = dwn; //static method //静态方法
    
var a = new myClass();
//dwn("私有方法: " + a.p('1')); //undefined 私有属性对象无法访问到
a.x('动态公有方法'); //10 公有方法
a.y('静态公有方法'); //20 静态公有方法
a.x = function(s){dwn("修改之后:" + s);};
a.y = function(s){dwn("修改之后:" + s);};
a.x("动态公有方法可以通过对象来改变");    
a.y("动态公有方法y覆盖了原型方法y");    
delete(a.x);
delete(a.y);
//a.x("删除动态公有方法之后");
a.y("动态公有方法y被删除后还原为原型方法y ")
//a.z("类方法无法通过对象访问:");
myClass.z("类方法");    
a.w();

-->
</script>
</body>
</html>
21.3继承与多态
21.3.1 什么是继承
一旦确定了两个类的继承关系,就至少意味着三层含义,一是子类的实例可以共享父类的方法,二是子类可以覆盖父类的方法或者扩展新的方法,三是子类和父类都是子类实例的类型。
21.3.2.1  构造继承法及其例子
这种继承方法的形式是在子类中执行父类的构造函数。
 
例21.14 构造继承法.html
  1. <html> 
  2.     <head> 
  3.         <title>Example-21.14 构造继承法</title>   
  4.     </head> 
  5.     <body> 
  6.         <script> 
  7.         <!-- 
  8.             function dwn(s){ 
  9.                 document.write(s + "<br/>");     
  10.             } 
  11.              
  12.             //定义一个Collection类型 
  13.             function Collection(size){ 
  14.                 this.size = function(){ return size; };  //公有方法,可以被继承 返回的是一个外部环境的参数 
  15.             } 
  16.              
  17.             Collection.prototype.isEmpty = function(){ //静态方法,不能被继承 
  18.                 return this.size == 0; 
  19.             } 
  20.              
  21.             //定义一个ArrayList类型,它“继承”Collection类型 
  22.             function ArrayList(){ 
  23.                 var m_elements = []; //私有成员,不能被继承 
  24.                 m_elements = Array.apply(m_elements,arguments); 
  25.                  
  26.                 //ArrayList类型继承Collection 
  27.                 this.base = Collection
  28.                 this.base.call(this,m_elements.length); 
  29.                  
  30.                 this.add = function(){ 
  31.                     return m_elements.push.apply(m_elements,arguments);  
  32.                 } 
  33.                  
  34.                 this.toArray = function(){ 
  35.                     return m_elements;   
  36.                 } 
  37.             } 
  38.              
  39.             ArrayList.prototype.toString = function(){ 
  40.                 return this.toArray().toString(); 
  41.             } 
  42.              
  43.             //定义一个SortedList类型,它继承ArrayList类型 
  44.             function SortedList(){ 
  45.                  
  46.                 //SortedList类型继承ArrayList 
  47.                 this.base = ArrayList
  48.                 this.base.apply(this,arguments); 
  49.                  
  50.                 this.sort = function(){ 
  51.                     var arr = this.toArray(); 
  52.                     arr.sort.apply(arr,arguments);   
  53.                 } 
  54.                  
  55.             } 
  56.              
  57.             //构造一个ArrayList 
  58.             var a = new ArrayList(1,2,3); 
  59.             dwn(a); 
  60.             dwn(a.size()); //a从Collection继承了size()方法 
  61.             dwn(a.isEmpty); //但是a没有继承到isEmpty方法 
  62.              
  63.             //构造一个SortedList 
  64.             var b = new SortedList(3,1,2); 
  65.             b.add(4,0);         //b 从ArrayList继承了add()方法 
  66.             dwn(b.toArray());   //b 从ArrayList继承了toArray()方法 
  67.             b.sort();            //b 自己实现的sort()方法 
  68.             dwn(b.toArray()); 
  69.             dwn(b);              //b没有继承ArrayList中重写的toString()方法 
  70.             dwn(b.size());       //b 从Collection继承了size()方法 
  71.              
  72.             //构造继承法不能满足子类和父类都是子类实例的“类型” 
  73.             dwn("构造继承法不能满足子类和父类都是子类实例的'类型':" + (a instanceof Collection)); 
  74.             dwn("构造继承法不能满足子类和父类都是子类实例的'类型':" + (b instanceof ArrayList)); 
  75.         -->  
  76.         </script> 
  77.     </body> 
  78. </html> 
运行结果:
1,2,3
3
undefined
3,1,2,4,0
0,1,2,3,4
[object Object]
3
构造继承法不能满足子类和父类都是子类实例的'类型':false
构造继承法不能满足子类和父类都是子类实例的'类型':false
 
例21.14 构造继承法修复SortedList的size方法中的bug.html
 
  1. <html> 
  2.     <head> 
  3.         <title>Example-21.14 构造继承法</title>   
  4.     </head> 
  5.     <body> 
  6.         <script> 
  7.         <!-- 
  8.             function dwn(s){ 
  9.                 document.write(s + "<br/>");     
  10.             } 
  11.              
  12.             //定义一个Collection类型 
  13.             function Collection(size){ 
  14.                 this.size = function(){ return size; };  //公有方法,可以被继承 返回的是一个外部环境的参数 
  15.             } 
  16.              
  17.             Collection.prototype.isEmpty = function(){ //静态方法,不能被继承 
  18.                 return this.size == 0; 
  19.             } 
  20.              
  21.             //定义一个ArrayList类型,它“继承”Collection类型 
  22.             function ArrayList(){ 
  23.                 var m_elements = []; //私有成员,不能被继承 
  24.                 m_elements = Array.apply(m_elements,arguments); 
  25.                  
  26.                 //ArrayList类型继承Collection 
  27.                 this.base = Collection
  28.                 this.base.call(this,m_elements.length); 
  29.                  
  30.                 this.add = function(){ 
  31.                     return m_elements.push.apply(m_elements,arguments);  
  32.                 } 
  33.                  
  34.                 this.toArray = function(){ 
  35.                     return m_elements;   
  36.                 } 
  37.                  
  38.                 this.size = function(){ return m_elements.length; } 
  39.             } 
  40.              
  41.             ArrayList.prototype.toString = function(){ 
  42.                 return this.toArray().toString(); 
  43.             } 
  44.              
  45.             //定义一个SortedList类型,它继承ArrayList类型 
  46.             function SortedList(){ 
  47.                  
  48.                 //SortedList类型继承ArrayList 
  49.                 this.base = ArrayList
  50.                 this.base.apply(this,arguments); 
  51.                  
  52.                 this.sort = function(){ 
  53.                     var arr = this.toArray(); 
  54.                     arr.sort.apply(arr,arguments);   
  55.                 } 
  56.                  
  57.             } 
  58.              
  59.             //构造一个ArrayList 
  60.             var a = new ArrayList(1,2,3); 
  61.             dwn(a); 
  62.             dwn(a.size()); //a从Collection继承了size()方法 
  63.             dwn(a.isEmpty); //但是a没有继承到isEmpty方法 
  64.              
  65.             //构造一个SortedList 
  66.             var b = new SortedList(3,1,2); 
  67.             b.add(4,0);         //b 从ArrayList继承了add()方法 
  68.             dwn(b.toArray());   //b 从ArrayList继承了toArray()方法 
  69.             b.sort();            //b 自己实现的sort()方法 
  70.             dwn(b.toArray()); 
  71.             dwn(b);              //b没有继承ArrayList中重写的toString()方法 
  72.             dwn(b.size());       //b 从Collection继承了size()方法 
  73.              
  74.             //构造继承法不能满足子类和父类都是子类实例的“类型” 
  75.             dwn("构造继承法不能满足子类和父类都是子类实例的'类型':" + (a instanceof Collection)); 
  76.             dwn("构造继承法不能满足子类和父类都是子类实例的'类型':" + (b instanceof ArrayList)); 
  77.         -->  
  78.         </script> 
  79.     </body> 
  80. </html> 
在ArrayList类中重写了size()方法:
 
  1. this.size = function(){ return m_elements.length; } 
运行结果:
1,2,3
3
undefined
3,1,2,4,0
0,1,2,3,4
[object Object]
5
构造继承法不能满足子类和父类都是子类实例的'类型':false
构造继承法不能满足子类和父类都是子类实例的'类型':false
 
这种继承关系是通过在子类中调用父类的构造函数来维护的。
这种继承方法除了通过调用父类构造函数将属性复制到自身之外,并没有做其他任何的事情,严格来说,它甚至算不上继承。尽管如此用它的特性来模拟常规的对象继承,也已经基本上达到了我们预期的目标。这种方法的优点是简单和直观,而且可以自由地用灵活的参数执行父类的构造函数,通过执行多个父类构造函数方便地实现多重继承,缺点主要是不能继承静态属性和方法,也不能满足所有父类都是子类实例的类型这个条件,这样对于实现多态将会造成麻烦。
 
 
 
21.3.2.2原型继承法及其例子
有人说,JavaScript的面向对象机制实际上是基于原型的一种机制,或者说,JavaScript是一种基于原型的语言。
基于原型编程是面向对象编程的一种特定形式。在这种基于原型的编程模型中,不是通过声明静态的类,而是通过复制已经
 
存在的原型对象来实现行为重用。这个模型一般被称作是class-less,面向原型,或者是基于接口编程。
 
 
基于原型模型其实并没有“类”的概念,这里所说的“类”是一种模拟,或者说是沿用了传统的面向对象编程的概念。
 
这种原型继承法和传统的类继承法并不一致。
 
prototype的最大特点是能够让对象实例共享原型对象的属性,因此如果把某个对象作为一个类型的原型,那么我们说这个
 
类型的所有实例以这个对象为原型。这个时候,实际上这个对象的类型也可以作为那些以这个对象为原型的实例的类型。

例21.15 原型继承法.html

  1. <html> 
  2.     <head> 
  3.         <title>Example-21.15 原型继承法</title>   
  4.     </head> 
  5.     <body> 
  6.         <script> 
  7.         <!-- 
  8.             function dwn(s){ 
  9.                 document.write(s + "<br/>");     
  10.             } 
  11.              
  12.             //定义一个Point类型 
  13.             function Point(dimension){ 
  14.                 this.dimension = dimension; 
  15.             } 
  16.              
  17.             //定义一个Point2D类型 
  18.             function Point2D(x,y){ 
  19.                 this.x = x; 
  20.                 this.y = y; 
  21.             } 
  22.             Point2D.prototype.distance = function(){ 
  23.                 return Math.sqrt(this.x * this.x + this.y * this.y); 
  24.             } 
  25.              
  26.             Point2D.prototype = new Point(2);  //Point2D继承了Point 
  27.              
  28.             //定义一个Point3D类型,也继承Point类型 
  29.             function Point3D(x,y,z){ 
  30.                 this.x = x; 
  31.                 this.y = y; 
  32.                 this.z = z;  
  33.             } 
  34.             Point3D.prototype = new Point(3);  //Point3D也继承了Point 
  35.              
  36.             //构造一个Point2D对象 
  37.             var p1 = new Point2D(0,0); 
  38.             //构造一个Point3D对象 
  39.             var p2 = new Point3D(0,1,2); 
  40.              
  41.             dwn(p1.dimension); 
  42.             dwn(p2.dimension); 
  43.             dwn(p1 instanceof Point2D); //p1是一个Point2D 
  44.             dwn(p1 instanceof Point);    //p1 也是一个Point 
  45.             dwn(p2 instanceof Point);    //p2是一个Point 
  46.         -->  
  47.         </script> 
  48.     </body> 
  49. </html> 

运行结果:

2
3
true
true
true

类型的原型可以构成一个原型链,这样就能实现多个层次的继承,继承链上的每一个对象都是实例的类型。

例21.16 prototype的多重继承.html

  1. <script> 
  2.     function dwn(s){ 
  3.             document.write(s + "<br/>");     
  4.     } 
  5.      
  6.     function Point(){ 
  7.         this.pointName = "Point"
  8.     } 
  9.     //Point继承Object,这个通常可以省略,因为自定义类型的缺省原型为Object 
  10.     Point.prototype = new Object(); 
  11.     function Point2D(){ 
  12.         this.point2DName = "Point2D"
  13.     } 
  14.     //Point2D继承Point 
  15.     Point2D.prototype = new Point(); 
  16.     function ColorPoint2D(){ 
  17.             this.colorPoint2DName = "ColorPoint2D"
  18.     } 
  19.     //ColorPoint2D又继承Point2D 
  20.     ColorPoint2D.prototype = new Point2D(); 
  21.      
  22.     var colorPoint2D = new ColorPoint2D(); 
  23.     dwn("colorPoint2DName:" + colorPoint2D.colorPoint2DName); 
  24.     dwn("point2DName:" + colorPoint2D.point2DName); 
  25.     dwn("pointName:" + colorPoint2D.pointName); 
  26. </script> 

运行结果:

colorPoint2DName:ColorPoint2D
point2DName:Point2D
pointName:Point

 

同构造继承法相比,原型继承法的优点是结构更加简单,而且不需要每次构造都调用父类的构造函数(尽管你仍然可以调用它),并且不需要通过复制属性的方式就能快速实现继承。但是它的缺点也是很明显的,首先它不方便直接支持多重继承,因为一个类型只能有一个原型;其次它不能很好地支持多参数和动态参数的父类构造,因为在原型继承的阶段你还不能决定以什么参数来实例化父类对象;第三是你被迫要在原型声明阶段实例化一个父类对象作为当前类型的原型,有的时候父类对象是不能也不应该随便实例化的;最后一个缺点是之前提到过的prototype的"副作用"。

 

同类继承相比,原型继承本来就是一个简化了的版本,因此我们不应该要求它完全达到标准的类继承的效果,实际上,当你的父类是一个简单、抽象的模型或者是一个接口的时候,原型继承的表现在已知的JavaScript对象继承中是最好的,甚至可以说,prototype继承才是JavaScript文法上提供的真正意义上的继承机制。所以,我们在使用JavaScript时,能够采用原型继承的地方,应当尽可能地采用这种继承方式。

 

 

面向对象不是只有类模型一种,prototype-based(基于原型)是class-based(基于类)的简化版本,是一种class-less的面向对象。对应地,prototype继承是class继承的简化版本,相对于class继承来说它简化了许多东西,例如省略了多重继承、基类构造函数、忽略了引用属性的继承......但不能因为它不支持这些特性,就不承认它是一种完整的继承,否则我们就在用class-based的眼光来看待prototype-based,实际上这可能是错误的。

其实prototype-based本来就是class-based的简化版,因此给继承加一个限制,要求父类必须是一个抽象类或者接口,那么prototype-based就没有任何问题了。当然了,也许这么做会使OOP的reuse(重用)能力减弱(以class-based的眼光来看),但是这可以通过其他机制来弥补,比如结合其他类型的继承方式,再比如闭包。

是否为继承添加额外的特性,开发者可以自由选择,但是在不需要这些额外特性的时候,还是有理由尽量用prototype-based继承。

总而言之,prototype-based认为语言本身可能不需要过分多的reuse能力,它牺牲了一些特性来保持语言的简洁,这没有错,prototype-based虽然比class-based简单,但它依然是真正意义上的object-oriented。

 

 21.3.2.3 实例继承法及其例子
由于构造继承法没有办法继承类型的静态方法,因此它无法很好地继承JavaScript的核心对象。而原型继承法虽然可以继承静态方法,但是依然无法很好地继承核心对象中的不可枚举方法。
 

21.17构造继承的局限性.html

  1. <html> 
  2.     <head> 
  3.         <title>例21.17构造继承的局限性</title>    
  4.     </head> 
  5.     <body> 
  6.         <script> 
  7.         <!-- 
  8.             function MyDate(){ 
  9.                 this.base = Date
  10.                 this.base.apply(this,arguments); 
  11.             } 
  12.             var date = new MyDate(); 
  13.             alert(date.toGMTString); 
  14.             //核心对象的某些方法不能被构造继承,原因是核心对象并不像我们自定义的一般对象那样 
  15.             //在构造函数里进行赋值或初始化操作 
  16.         -->  
  17.         </script> 
  18.     </body> 
  19. </html> 

 21.18 原型继承的局限性.html

  1. <html> 
  2.     <head> 
  3.         <title>例21.18 原型继承的局限性</title>   
  4.     </head> 
  5.     <body> 
  6.         <script> 
  7.         <!-- 
  8.             function MyDate(){} 
  9.             MyDate.prototype = new Date(); 
  10.             var date = new MyDate(); 
  11.             alert(date.toGMTString); 
  12.             date.toGMTString();//Date.prototype.toUTCString called on incompatible Object 
  13.         -->  
  14.         </script> 
  15.     </body> 
  16. </html> 

我的想法:核心对象Date根本就是用C语言或者其他语言实现的,js本身的继承怎么能够很好的继承呢?

构造函数通常没有返回值,它们只是初始化由this值传递进来的对象,并且什么也不返回。如果函数有返回值,被返回的对象就成了new表达式的值。

111