JavaScript面向对象程序设计 ( 6 章 )_htmlJavaScript面向对象程序设计 ( 6 章 )_父类_02好的办法
 1  //*******************111111构造函数+原型模型( JQuery使)*******************
 2  function Person(name, age, job){
 3     this.name = name;            // 此时的this,是指创建对象时的该对象
 4     this.age = age;
 5     this.job = job;
 6  }
 7  
 8  Person.prototype = {
 9     constructor : Person,        // 将 constructor又指向了Person, 否则将指向Object
10     sayName : function() {
11         alert(this.name);        // this, 谁调用就是谁
12     }
13  };
14  
15  //*******************222222继承的方法 组合继承(经典继承)**************************
16 function SuperType( name ) {
17     this.name = name;
18     this.colors = ["red","blue", "green"];
19     
20 }
21  SuperType.prototype.sayName = function() {
22     alert(this.name);
23 };
24  
25 function SubType(name, age) {
26     SuperType.call(this, name);            //第二次调用 SuperType() 
27     
28     this.age = age;
29 }
30 SubType.prototype = new SuperType();    //第一次调用 SuperType()
31 
32 SubType.prototype.sayAge = function() {
33     alert(this.age);
34 };

Javascript 大神们都不推荐使用面向对象方法编写javascript程序, 也就是说, 大神不推荐使用"继承", jquery中就没有使用继承

好的继承方法,如上例子(图表如下)

0. 在javascript 的最新标准中, 增加了一些内容, 例如可以对象的属性

数据属性

[[Configurable]]: 能够通过 delete 删除属性, 能否修改属性等等, 默认是 true

[[Enumerable]] : 能否通过 for -in 循环遍历属性

[[Writable]]: 能否被修改的属性值, 默认为 true

[[Value]]: 属性的数据值, 默认为undefined.

例如:

var person = {};

Object.defineProperty( person, "name", {  // 传递的参数有对象名, 和属性名

  writable: false,

  value: "Nichloas"

});

alert(person.name); // "Nicholas"

person.name = "Greg";

alert(person.name); // "Nicholas"

看到以上的例子, 可以确定, 这样就没有办法随便修改对象中的属性, 但是貌似这种方法... 也不是很好, 如果是考虑封装性的话, 在 setter 中可以修改该属性, 那岂不是要在setter中再设置这个writable 属性一次.

 

1.创建对象

JavaScript 把对象定义为 : 无序属性的集合, 其属性可以包括基本值, 对象或者 函数.

每个对象都是基于一个引用类型创建的.(  引用类型可以是原生的, 也可以是开发人员创建的 )

创建自定义对象的最简单方式就是创建一个 object 的实力, 再添加属性和方法. 例如 :

   1:  var person = new Object() ;  
   2:    
   3:  person.name = "Nicholas" ;  
   4:    
   5:  person.age = 29;  
   6:    
   7:  person.job = "Software Engineer" ;  
   8:    
   9:  person.sayName = function () {  
  10:    
  11:     alert( this.name );    --> this.name 被解析成 person.name  
  12:    
  13:  }  

早期的JavaScript开发人员经常使用这个模式创建新对象,  但这种方式有个明显的缺点, 使用同一个接口创建很多对象, 会产生大量的重复代码, 为解决这个问题, 人们开始使用工厂模式的一种变体

工厂模式 ( 利用函数封装创建对象 )
   1:  function createPerson (name, age, job){
   2:   
   3:      var o = new Object ();
   4:   
   5:      o.name = name;
   6:   
   7:      o.age = age;
   8:   
   9:      o.job = job;
  10:   
  11:      o.sayName = function(){
  12:   
  13:        alert(this.name) ;
  14:   
  15:      } ;
  16:   
  17:      return o ;
  18:   
  19:  }
  20:   
  21:  var person1 = createPerson( "Nicholas", 29, "Software Engineer") ;
  22:   
  23:  var person2 = createPerson( "Greg" , 27 , "Doctor" ) ;
  24:   
  25:  person1.sayName() ;
  26:   
  27:  person2.sayName();

工厂模式很好, 但却没有解决对象识别的问题 ( 即怎么样知道一个对象的类型) , 进尔出现了构造函数模式.

构造函数模式
   1:  <SPAN style="COLOR: #000000">function Person (name, age, job) {
   2:   
   3:    this.name = name ;
   4:   
   5:    this.age = age ;
   6:   
   7:    this.job = job ;
   8:   
   9:    this.sayName = function(){
  10:   
  11:      alert( this.name); 
  12:   
  13:    };
  14:   
  15:  }
  16:   
  17:  var person1 = new Person("Nicholoas", 29, "Software Engineer" ) ;         // 构造函数有个 new
  18:  </SPAN>

与上边的CreatePerson()区别 : 没有显示的创建对象, 直接将属性和方法赋给了 this 对象. 没有 return 语句

要创建一个 Person 的新实例, 必须使用 new 操作符,  以这种方式调用构造函数. 会有4个动作;

1)  创建一个新对象;

2)将构造函数的作用域赋给新对象( 因此 this 就指向了这个新对象)

3)执行构造函数中的代码( 为这个新对象添加属性);

4)返回新对象

以这种方式定义的构造函数是定义Global对象( 在浏览器中是 window对象)

任何函数, 只要通过 new 操作符来调用, 那么它就可以作为构造函数. 而任何函数, 如果不通过new 操作符号来调用, 那它跟普通函数没什么两样.

   1:  var pserson = new Person ( "Nicholas", 29 ,"Software" ) ;        // 当作构造函数
   2:   
   3:  Pserson( "Greg" , 27, "Doctor" ) ;  // 作为普通函数, 添加到 windows  
   4:   
   5:  var o = new Object() ;                         // 在另一个对象的作用域中调用
   6:   
   7:  Person.call( o, "Kristen" , 25, "Nurse" ) ;   // call 是每一个函数都有的方法
   8:   
   9:  o.sayName() ;           // Kristen
  10:   
  11:  alert( person1.sayName == person2.sayName)      // false

以上person1.sayName == person2.sayName 是 false。。所以

将方法从构造函数中移出

因为构造函数中的方法实例并不是来自一个类, 所以它们是不同的方法,

   1:  function Person ( name, age, job ){
   2:   
   3:    this.name = name ;
   4:   
   5:    this.age = age ;
   6:   
   7:    this.job = job ;
   8:   
   9:    this.sayName = sayName ;   // 注意此时的 sayName右边没有括号, 表示将 sayName作为一个指针变量进行赋值, 而并非一个函数, 需要执行.
  10:   
  11:  }
  12:   
  13:  function sayName() {
  14:   
  15:    alert( this.name) ;
  16:   
  17:  }
  18:   
  19:  var person1 = new Person("Nicholas" , 29, "Software Engineer") ;
  20:   
  21:  alert( person1.sayName == person2.sayName )   // true

这时,person1.sayName == person2.sayName 返回 true

这样, 方法来自于一个类, 但是... 此时 sayName实际上是个全局函数, 但是只是该类来调用, 所以就失去了"全局函数"的实质. 即, 在全局作用域中定义的函数实际上只能被某个对象调用, 这让全局作用域有点名不副实.更让人无法接受的是: 如果对象需要定义很多方法, 那么就要定义很多个全局函数, 于是我们这个自定义的引用类型就丝毫没有封装性可言.这样就引如了原型模式....

原型模式

每个函数都有一个 prototype(原型)属性, 这个属性是一个指针, 指向一个对象, 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法, 注意: 是针对所有实例, 区别于静态方法和静态属性, 如果按照字面意思来理解, 那么 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象.使用原型的好处是可以让所有对象实例共享它所包含的属性和方法。

   1:  function Person(){
   2:      
   3:  }
   4:  Person.prototype.name = "Nicholas";
   5:  Person.protptype.age = 29;
   6:  Person.prototype.job = "Software engineer";
   7:  Person.prototype.sayName = function(){
   8:      alert(this.name);
   9:  };
  10:   
  11:  var person1 = new Person();
  12:  person1.sayName();        //"Nicholas"
  13:   
  14:  var person2 = new Person();
  15:  person2.sayName();        //"Nicholas"
  16:   
  17:  alert(person1.sayName == person2.sayName);        //true

理解原型,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,默认情况下,prototype属性都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。上例中:Person.prototype.constructor 就指向了 Person。而通过这个构造函数,我们还可以继续为原型增添其他属性和方法。

当调用构造函数创建一个实例,该实例的内部包含一个指针,指向构造函数的原型属性。一般叫做 _proto_,此时可以从图中看出,实例与构造函数没有直接关系了。

JavaScript面向对象程序设计 ( 6 章 )_原型链_03

更简单的原型语法
   1:  function Person(){
   2:   
   3:  }
   4:   
   5:  Person.prototype = {                      // prototype 是一个对象, 这样子定义, 相当于重写了 prototype 对象
   6:   
   7:      constructor : Person ,              // 特殊声明
   8:   
   9:       name : "Nicholas" ,
  10:   
  11:       age : 29 ,
  12:   
  13:       job : "Software Engineer" ,
  14:   
  15:       sayName : function(){
  16:   
  17:             alert( this.name );
  18:   
  19:        }
  20:   
  21:  } ;

最终结果与上边的原型 相同, 但有一个例外 : constructor 属性不再指向 Person了, ( 每创建一个函数, 就会同时创建它的 prototype 对象, 这个对象自动获得 constructor 属性, 而我们 在这里使用的语法, 本质上完全重写了默认的prototype对象, 因此 constructor 属性也就变成了新对象的 constructor 属性( 指向 Object构造函数), 不再指向Person函数. 所以,我们写了代码 : constructor : Person , 这样就又将constructor指向Person。

由于在原型中查找值是一次搜索, 因此我们对原型对象所做的任何修改都能立刻从实例上反映出来-即使是先创建了实例后修改原型也是一样. ( 看图就知道, 是共享的内容, 所以肯定是一改变就全部都改变, 即便是先创建的对象, 因为指针指的位置没有变, 单个增加的函数也是一样 )

当我们调用实例的一个方法时, 比如 person.sayHi() , 首先会在实例中搜索名为 sayHi的属性, 在没找到的情况下, 会继续搜索原型 .因实例与原型之间的连接只不过是一个指针, 而非一个副本, 因此就可以在原型中找到新的sayHi属性并返回保存在那里的函数.

实例与原型之间是松散连接

尽管可以随时为原型添加属性和方法, 并且修改能够立即在所有对象实例中反映出来, 但是如果重写整个原型对象, 那么情况就不一样了, 我们知道, 构造函数会为实例添加一个指向最初原型的 _proto_ 指针, 请记注: 实例中的指针仅指向原型, 而不指向构造函数

1) 创建实例, 构造函数会为这个实例的指针 _proto_ 分配原型函数地址.

2) 原型函数被重写, 原型函数地址变更. ( 此时构造函数中的prototype指针变更为新的原型对象的指针, 因为在重写时语句是 Person.prototype = {}; 这说明什么, 就是将新的原型对象的指针赋值给 Person.prototype 指针啊. )

3) 已经创建的实例的 _proto_ 指针还指向原来的( 未重写之前) 的原型对象.

4) 所以当再调用时, 就会出错. ( 新创建的实例就不会有问题, 因为新创建的实例的_proto_ 属性是新分配的, 是正确的)

5) 所以, 已经有创建实例的原型就不能在用字面量方法, 即完全重写原型.

   1:  function Person(){
   2:      
   3:  }
   4:  var person = new Person();
   5:   
   6:  person.prototype = {
   7:      constructor : Person,
   8:      name : "Nicholas",
   9:      age : 29,
  10:      job : "Software Engineer",
  11:      sayName : function(){
  12:          alert(this.name);
  13:      }
  14:  };
  15:   
  16:  person.sayName();        //error

JavaScript面向对象程序设计 ( 6 章 )_原型链_04

 

原型对象的问题, 它省略了为构造函数传递初始化参数这一环节, 结果所有实例在默认情况下都将取得相同的属性值, 尤其是引用类型, 比如数组会有问题.所以就有构造+原型。

构造模式与原型模式相结合 ( 比较好的方法 )   -- 先使用这种吧 , 强烈推荐
   1:  function Person(name, age, job){      // 不想要所有实例都共享的内容, 就写在构造函数中
   2:   
   3:     this.name = name ;
   4:   
   5:     this.age = age ;
   6:   
   7:     this.job = job; 
   8:   
   9:     this.firends = [ "Shelby" , "court"] ;    // 尤其是这种引用类型
  10:   
  11:  }
  12:   
  13:  Person.prototype = {                               // 使用这种字面量时, prototype会被重写, 所以尽量在定义时使用这种方法, 定义之后, 当需要新添加方法时, 不要使用这种方法
  14:   
  15:    consturctor : Person ,                           // 接上,而是直接添加方法 Person.prototype.addMethod = function(){} ;  如果不指定 constructor : Person , 默认的就会是Object.
  16:   
  17:    sayName : function() {                          // 想要所有实例都共享的内容, 就把它写在 Person.prototype中.
  18:   
  19:      alert( this.name ) ;
  20:   
  21:    }
  22:   
  23:  } ;

这样, 不需要共享的就保存在构造函数中, 需要共享的就保存在 prototype 中. 即分开。

   1:  alert( person1.firends === person2.friends)  //false
   2:   
   3:  alert( person1.sayName === person2.sayName ) ; // true
动态原型模型

以上, 已经做很好了。只是有其他OO语言经验的开发人员在看到嘟噜的构造函数和原型时, 会感到困惑(应该是在一起的 ) , 所以才用动态原型模型, 说白了就是将判断增加内容写在构造函数中.

   1:  function  Person( name, age, job ) {
   2:   
   3:      this.name = name;
   4:   
   5:      this.age = age;
   6:   
   7:      this.job = job;
   8:   
   9:      if ( typeof this.sayName != "function" ) {
  10:   
  11:        Person.prototype.sayName = function(){
  12:   
  13:               alert( this.name ) ;
  14:   
  15:        };
  16:   
  17:      }
  18:   
  19:  }
  20:   
  21:  var person = new Person ( "Nicholas" , 29, "Software engineer" );
  22:   
  23:  person.sayName() ;

注意构造函数中对方法的定义, 这里只在 sayName() 方法不存在的情况下, 才会将它添加到原型中, 这段代码只会在初次调用构造函数时才会执行, 伺候, 原型已经初始化, 不需要再做什么修改了, 不过要记住, 这里对原型所做的修改, 能够立即在所有实例中得到反映.

稳妥安全模型

适合在某些需要安全环境中使用, 如上, 在构造函数中定义的所有属性, 都可以在外部轻松访问和修改, 但是有很多属性, 我们想做为私有变量不需要别人修改, 这时候可以使用稳妥安全模型,例如:

function Person(name, age, job) {

  var o = new Object();    // 创建要返回的对象

  var private_value = 10; // 注意这个变量, 没有在返回对象的o内, 但是由于和o在一个函数内, 即在一个环境中, 所以o 的方法可以访问到该变量

  o.sayName = function() {

    alert(private_value);  // 这个方法可以访问到刚刚的内部变量

  }

  return o;

}

2. 继承

每个构造函数都有一个原型对象prototype, 原型对象包含一个指向构造函数的指针constructor, 而实例都包含一个指向原型对象的内部指针_proto_ 。

Javascript中只有实现继承, (实现继承名词解释: 即没有那种, 只定义一个函数接口名称, 没有函数内容实现的继承,比如Java中的接口 ) , 而且是通过原型链来实现的继承.

简单回顾一下, 构造函数, 原型 和实例的关系:

每个构造函数都有一个原型(每个函数都有一个原型, 更别说构造函数了), 原型对象包含一个指向构造函数的指针constructor, 而实例都包含一个指向原型对象的指针.

那么, 假如我们让原型对象等于另一个类型的实例, 结果会怎么样? 原型链

   1:  function SuperType(){
   2:      this.property = true; 
   3:  }
   4:   
   5:  SuperType.prototype.getSuperValue = function(){
   6:      return this.property;    
   7:  };
   8:   
   9:  function SubType(){
  10:      this.subproperty = false;
  11:  }
  12:   
  13:  //继承了SuperType
  14:  SubType.prototype = new SuperType();    //证明,SubType.prototype是SuperType的一个实例,既然是一个实例,那么就又_proto_指针,指向SuperType的prototype,同时它也是一个prototype
  15:   
  16:  SubType.prototype.getSubValue = function(){
  17:      return this.subproperty;
  18:  };
  19:   
  20:  var instance = new SubTYpe();
  21:  alert(instance.getSuperValue());        //true
  22:  alert(instance. instanceof Object);        //true
  23:  alert(instance instanceof SuperType);    //true
  24:  alert(instance instanceof SubType);        //true

JavaScript面向对象程序设计 ( 6 章 )_构造函数_05

new SupperType() 构造函数创建了supperType的实例, 并将此实例赋给了 subType.prototype . 实现的本质是重写原型对象( 即 subType.prototype)代之以一个新类型的实例( new SupperType() 这样, 原来存在于 superType中的实例的所有属性和方法现在也都存在于 subType.prototype中.并且此时由于没有补充constructor=SubType, 所以,此时的constrctor就指向了SupperType(), 假如supper.prototype 又是另外一个对象的实例, 那么, 就会出现层层递近的现象, 所有引用类型都是通过这种原型链的机制实现的, 所有引用类型默认都继承了Object.)

注意, constructor, 本身 constructor 是原型中的一个属性, 所以 constructor 是可以共享的. 而 SubType 是 SupperType的一个实例, 所以它从 SupperType原型那边共享了 SupperType 原型的 constructor.

问题, 因为可以通过 constructor 找到构造函数, 而构造函数在面向对象中是十分有用的, 比如实例的私有变量, 可以通过构造函数来初始化, 但是如果是找到了父类的 constructor, 那么也就找到了父类的构造函数, 用父类的构造函数来初始化子类的实例么 ?

如果你搜索一个实例的比如一个方法, 那么搜索的过程是这样的, 1) 搜索实例本身, 2) 搜索 SubType.prototype 3) 搜索 SupperType.prototype, 然后再搜索 Super的Super...

 

不要忘记 Object类型

JavaScript面向对象程序设计 ( 6 章 )_父类_06

可以重写父类的代码, 但是没有重载 ( 子类型会重写父类型的方法, 方法名相同的情况下, 如果出现重写, 通过SubType的实例调用该方法就是重写后的方法, SupperType实例调用该方法,则还是没有重写之前的方法.

如上图, SuperType.prototype 有 constructor 属性, 但是 SubType.prototype 就没有 constructor 属性, 很明显, 上文中说过, 它继承 SuperType.prototype的constructor属性, 那为什么SuperType 不继承 Object.prototype.constructor 属性呢 ?

这里要注意 : 想要重写的方法和新增加的方法必须写在 SubType.prototype = new SuperType(); 这条语句之后. 还有就是不能使用 SubType.prototype = {}; 这种能够重写原型链的字面量形式.

确认原型和实例的关系, instanceof , Object.prototype.isPrototypeOf(instance) , 继承关系都会返回 true

例子 : var zhang = new Person("ZhangSan", "man");

此时,虽然有 new , 只是开辟了实例的内存空间,并通过指针进行连接的,即原型链的方式,

当代码var zhang = new Person("ZhangSan", "man")执行时,其实内部做了如下几件事情:

  • 创建一个空白对象(new Object())。
  • 拷贝Person.prototype中的属性(键值对)到这个空对象中(我们前面提到,内部实现时不是拷贝而是一个隐藏的链接)。
  • 将这个对象通过this关键字传递到构造函数中并执行构造函数。
  • 将这个对象赋值给变量zhang。
原型链的问题

原型链在继承时, subType.prototype = new SuperType() ; 此时 原型链由于继承的关系, 会将 subType当作是 SuperType的一个实例, 这样, superType中的属性,即便是这些属性在构造函数中,而不是prototype中,(即 不想被继承)但是也会被继承。例如数组也会被继承, 这样就存在着之前说的问题, 引用类型的问题...跟前面一样...例如 : ( color 是在构造函数中,而非prototype中 )

   1:  <SPAN style="COLOR: #000000">function SuperType(){
   2:   
   3:    this.color = [ "red", "blue", "green"] ;        // 注意, 此处特意将引用类型写在构造函数中, 而不是 prototype( 原型中 )
   4:   
   5:  }
   6:   
   7:  function SubType(){
   8:   
   9:  }
  10:   
  11:  // 继承了 SuperType
  12:   
  13:  SubType.prototype = new SuperType() ;
  14:   
  15:  var instance1 = new SubType() ;
  16:   
  17:  instance1.colors.push("black") ;      // "red","blue","green","black"
  18:   
  19:  alert( instance1.colors );
  20:   
  21:  var instance2 = new SubType() ;     // 一个另外的独立的对象
  22:   
  23:  alert ( instance2.colors );                 // "red","blue","green","black"
  24:  </SPAN>

当SubType通过原型链继承了SuperType之后, SubType.prototype就变成了SuperType的一个实例,因此就有了color属性.

在父类构造函数中的属性, 例如 color数组, 在继承时, 因为是通过父类的实例, 所以这个属性也顺理成章的被继承.进而成为子类的prototype( 注意color不是在父类的prototype中,但是由于继承,跑到了子类的prototype中,这样,所有的子类再创建实例时就会共享这个数组color, 这是不可以的.

另一个问题 : 在创建子类型的实例时, 不能向超类型的构造函数中传递参数,

所以, 实践中, 很少单独使用原型链.

借用构造函数技术 ( 解决以上2个问题 )

在子类型构造函数的内部调用超类型的构造函数, ( 可以向父类的构造函数中传递参数 )

   1:  function SubType(){
   2:   
   3:    SuperType.call(this , "参数") ;   // 调用父类构造函数
   4:   
   5:    this.age = 29 ;                             // 子类的成员
   6:   
   7:  }

这样做, 虽然在继承时, 原型链将数组等内容改变, 但是通过调用父类的构造函数, 又重新初始化了一般数组类型, 进而得到正确的结果.

说白了, 就是父类原型链继承是先说这是个"苹果", 后面子类再调用构造函数说这是个"香蕉", 但是这样还是不好, 有些属性, 我压根就不想继承, 这个方法只是解决了继承过来的对错问题, 但是对不想继承, 还是没有办法.

例如 color[] 这个数组, 开始颜色是 "red" ,"blue","green" ,  当在父类的一个实例中增加一个颜色 "black" , 而恰巧该实例被赋予给子类的原型, subType.prototype = new SupperType() ;  这时由于继承关系, subType中的数组默认就是 "red" , "blue" , "green" , "black" , 这是错的, 此时可以重新调用父类的构造函数来初始化这个数组为 "red","blue","green".

经典继承 ( 最常用 )又叫组合继承  会调用2次父类的构造函数 最常使用

将原型链和借用构造函数技术组合 ( 使用原型链实现对原型属性和方法的继承, 而借用构造函数来实现对实例属性的继承 )

( 方法全部写在 prototyoe 中, 属性全部写在 构造函数中 ) , 只是会调用2次父类的构造函数.

这种调用2次父类构造函数的情况, 实际上是一个屏蔽的过程,即子类实例中的color数组屏蔽了子类prototype中的属性color .

ECMAScript 5 新增的 Object.create()方法, 规范原型链继承, 这个方法接收两个参数, 一个用作新对象原型的对象 和 (可选的)一个为新对象定义额外属性的对象, 在传入一个参数的情况下, Object.create() 与 object() 方法相同.

var person = {

  name: "Nicholas",

  friends: ["shelby", "court", "Van"]

};

var anotherPerson = Object.create(person);

anotherPerson.name = "Greg";

anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);

yetAnotherPerson.name = "Linda";

yetAnotherPerson.friends.push("Barbie");

 

alert(person.friends); // "Shelby, Court, Van, Rob, Barbie"

寄生组合式继承 ( 开发人员普遍认为这种继承是最理想的 ) 

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

其实, 原来的思想是将 SubType.prototype = new SupperType() ; 即将父类的一个实例赋予子类的prototype, 但是, 父类的实例并不是"干净" 的, 有可能这个实例有自己的引用类型color, 而此时会一同继承, 所以, 现在的这种方法说就是 使用 subType.prototype = superType.prototype. 并且 subType.prototype.constructor = subType;

别的都一样, 只是将 var subType.prototype = new SuperType() 替换成 inheritPrototype( subType, SuperType );

   1:  function inheritPrototype(subType, superType){      // subType, superType 是2个构造函数
   2:   
   3:    var portotype = object(superType.prototype) ;         // 注意: 此时创建 superType.prototype的副本, 开辟心内存
   4:   
   5:    prototype.constructor = subType ;                             // 因为重新定义了, 所以要重新定义 prototype.constructor
   6:   
   7:    subType.prototype = prototype ;                                // 最后将有继承信息的prototype 赋予给subType.prototype
   8:   
   9:  }
这样, 就只调用了一次构造函数 . 

JavaScript面向对象程序设计 ( 6 章 )_父类_07

这个例子中的 inheritPrototype() 函数实现了继承组合继承的最简单形式, 在函数内, 第一步是创建超类原型的一个副本, 第 2 步是为这个副本添加 constructor 属性, 从而弥补因重写原型而失去的默认的 constructor属性, 最后一步, 将新创建的对象(副本) 赋给子类的原型, (这样的原型副本是干净的, 继承中没有多余属性会被继承)

 

JavaScript面向对象程序设计 ( 6 章 )_html_08