原型链,借用构造函数,组合式,寄生式, 寄生组合式

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法.

其基本思想就是  利用原型让一个引用类型 继承另一个引用类型的属性和方法.

实现原型链有一种基本模式,大致如下:

function SuperType() { //定义父类             this.property = true;          }         SuperType.prototype.GetSuperValue = function () {  //原型方法             return this.property;         }          function SubType() { //定义子类             this.SubProperty = false;         }         //继承父类         SubType.prototype = new SuperType();          SubType.prototype.getSubValue = function () {             return this.SubProperty;         }         //实例化         var instance = new SubType();         alert(instance.GetSuperValue());  //true
    alert(Object.getOwnPropertyNames(SubType.prototype)); //property ,GetSubValue         alert(Object.getOwnPropertyNames(SuperType.prototype));  //constructor,GetSuperValue
    alert(instance.constructor.name);  //SuperType
    

这里例子内的 实例以及构造函数和原型的关系如下图:

image

下面解释一下这个图的大概意思:

我们这里没有使用SubType的默认原型, 而是用SuperType的实例代替了他的默认原型.于是,新原型 就具备的一个 SuperType实例的

所有属性和方法,还拥有一个 指向SuberType 原型对象的一个指针. 最终的结果就导致:instance指向SubType Prorotype,而 SubType Prototype  指向 SuperType Prototype  . GetSuperType()方法仍然还在SuperType.prototype中,但是 property则位于SubType.prototype中. 这是因为property是一个实例属性,而getSuperValue()则是个原型方法. 既然 SubType.prototype是 SuperType的实例, 那么property当然位于该实例中了.

从上面的代码可以看出 instance 的构造函数已经变成了 SuperType,而不是SubType.那么原因就在于,SubType的原型对象被 改成了

一个 SuperType的实例, 那么 SubType的原型对象就 指向了 SuperType的原型,而SuperType的原型的构造函数指向了 SuperType,

所以instance的构造函数变成了 SuperType.

 

事实上,引用类型都默认继承了 Object,而这个继承也是 原型链来实现的. 所以所有函数的默认原型 都是Object的实例,因此默认原型

都会包含一个指向 Object.prototype的指针.

 

确定 原型和实例的关系

可以通过使用 instanceof操作符,也可以使用 isPrototypeOf()方法,下面看例子:

    alert(instance instanceof SubType);  //true         alert(SubType.prototype.isPrototypeOf(instance));  //true

定义方法需要注意的

子类有时候要重写超类的方法,或对超类进行扩充.那么 给原型添加方法的代码要 放在 替换原型的语句之后.看下面的例子:

function SuperType() {  //定义超类             this.property = true;  //定义属性             if (typeof this.GetSuperValue != "function") {                 SuperType.prototype.GetSuperValue = function () { //定义原型方法                     return this.property;                 }             }         }          function SubType() {             this.SubProperty = false;         }         //继承超类         SubType.prototype = new SuperType();         SubType.prototype.GetSubValue = function () { //定义子类的方法,对父类实现扩展             return this.SubProperty;         }          SubType.prototype.GetSuperValue = function () { //重写超类的方法             return false;         }          var in1 = new SubType();         alert(in1.GetSuperValue()); //false         alert(in1.GetSubValue()) //false          var in2 = new SuperType();         alert(in2.GetSuperValue());  //true

 

还有就是通过 原型链继承时,不能使用对象字面量 创建原型方法.

function SuperType() {             this.property = true;             if (typeof this.GetSuperValue != "function") {                 SuperType.prototype.GetSuperValue = function () {                     return this.property;                 }             }         }          function SubType() {             this.SubProperty = false;         }         //继承超类         SubType.prototype = new SuperType();                  //使用对象字面量添加 新方法,会导致上一行代码无效.         SubType.prototype = {              getSubValue: function () {                 return this.SubProperty;             },             getValue: function () {                 return false;             }         }          var in1 = new SubType();         alert(in1.getSuperValue()); //错误

以上代码 SubType.prototype = new SuperType();  刚刚把 超类的实例赋给子类的原型, 紧接着又将子类的原型替换成了

一个对象字面量.

由于现在的原型包含的是一个 Object的实例,而不是 超类的实例,所以 现在 SubType和 SuperType已经没有关系了.

原型链的问题:

1.最主要的问题来自引用类型值的原型. 包含引用类型值的原型属性 会被 所有实例共享,在上一节 已经演示过了.这正是 在构造函数中定义

属性的原因.

下面看例子:

 function fc() {             this.colors = ["red", "green"];         }         function sc() {         }         sc.prototype = new fc();          var instance1 = new sc();         instance1.colors.push("blue");         alert(instance1.colors); //red,green,blue          var instance2 = new sc();         alert(instance2.colors); //red,green,blue

 

两个实例的 colors值 是一样的.

 

借用构造函数

在解决原型中包含引用类型值的问题过程中,使用借用构造函数的技术.

基本思想就是: 在子类型构造函数的内部 调用超类型的构造函数.

下面看一个实例:

function SuperType() {             this.colors = ["red", "green"];         }          function SubType() {             SuperType.call(this); //继承了SuperType         }          var in1 = new SubType();         in1.colors.push("blue");         alert(in1.colors);   //red,green,blue          var in2 = new SubType();         alert(in2.colors);  //red,green

 

代码中 SuperType.call(this) 借调了 超类型的构造函数,通过使用call()或者 apply()方法,实例化SubType的环境下 调用 SuperType的构造函数,这样一来, 就会在新的Subtype对象执行SuperType()函数 中定义的对象初始化代码, 结果每个SubType实例都会有具有自己的colors属性的副本.

 

传递参数:

相对于原型链而言, 借用构造函数的另一个优势,就是在实例化子类 对象的时候,可以向超类构造函数传递参数,下面看例子:

function SuperType() {             this.name = arguments[0];         }          function SubType() {             SuperType.call(this,"gao");         }          var in1 = new SubType();         alert(in1.name);  //gao

 

借用构造函数的问题

和构造函数模式一样, 函数定义的 方法不能够重用,每次创建一个实例,都会创建一个方法的实例,那么将会占用大量的资源.那么就会用

到下面的组合继承了.

 

组合继承

就是结合了 原型链和借用构造函数的优点.

思想就是:使用原型链 继承方法,而用借用构造函数继承属性. 这样既可以通过 在原型上定义方法,实现复用, 通过构造函数定义属性,

可以使每一个实例 都拥有自己的 独有属性.下面看一个例子:

function SuperType(name) {             this.name = arguments[0]; //定义属性             if (typeof this.sayName != "function") {                 SuperType.prototype.sayName = function () {//定义原型方法                     alert(this.name);                 }             }         }          function SubType(name, age) {             SuperType.call(this, name); //继承属性             this.age = age;         }         SubType.prototype = new SuperType(); //继承方法         SubType.prototype.sayAge = function () { //扩展父类的方法,要在 继承父类之后定义             alert(this.age);         }          var in1 = new SubType("gao", 12);         in1.sayName();  //gao         in1.sayAge();  //12          var in2 = new SubType("gao1");         in2.sayName();  //gao1
    in2.sayAge();  //undefined

 

我们看到实例间没有共享属性,但是共享了方法.

 

原型式继承

原型式继承的模型如下:

function object(o) {             function F() { };             F.prototype = o;             return new F();         }

 

将object函数传进的对象参数 作为 F构造函数的原型.本质上说,是对传进的参数对象的 一次浅复制,看如下代码:

function object(o) {             function F() { };             F.prototype = o;             return new F();         }          var Person = { //创建一个基础对象             name: "gao",             friends:["joe","bob"]         }                  var newPerson = object(Person);  //实现继承         alert(newPerson.name); //gao         newPerson.friends.push("lily");          alert(Person.friends); //joe,bob,lily

ECMAScript 5通过新增的Object.Create()方法 规范化原型式继承.这个方法接受两个参数,第一个是 用作新对象的原型,另一个是新对象定义额外属性的对象.

image

下面看一个参数的情况:

var Person = {             name: "gao",             friends:["joe","bob"]         }                  var newPerson = Object.create(Person);         alert(newPerson.name);  //gao

image

下面看有两个参数的情况:

 var Person = {             name: "gao",             friends: ["joe", "bob"]         }          var newPerson = Object.create(Person, {             name: {                 value: "newgao"             },             age: {                 value: 12             }         });         alert(newPerson.name);  //newgao         alert(newPerson.age);    //12

image

这里还要重复一下,就是原型模式的一个特性,就是  所有实例都会共享原型的 引用类型的属性.

 

寄生式继承

此种继承的思路 与 寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数内部 以某种方式增强对象,最后在像真地是它做了所有工作一样,返回对象. 看代码示例:

function co(o) {             var clone = Object.create(o);             clone.sayHi = function () {                 alert("hi");             }             return clone;         }         var people = {             name:"gao"         }          var p1 = new co(people);         p1.sayHi(); //hi

使用寄生式继承为对象添加函数,会由于不能做到函数的复用而降低效率,这一点与 寄生构造函数模式 类似.

 

寄生组合式继承

组合继承是 Javascript最常用的继承模式,但是也有不足之处.就是 无论在什么情况下,都会调用两次超类的构造函数.下面回顾一下组合继承:

function SuperType() {             this.property = true;             if (typeof this.SaySuperValue != "function") {                 SuperType.prototype.SaySuperValue = function () {                     return this.property;                 }             }         }          function SubType() {             this.SubProperty = false;             SuperType.call(this);  //第一次调用超类构造函数         }         SubType.prototype = new SuperType();  // 第二次调用构造函数         SubType.prototype.SaySubValue = function () {             return this.SubProperty;         }          var in1 = new SubType();         alert(in1.SaySuperValue());  //true         alert(in1.SaySubValue());  //false

下面看一下 寄生组合式继承的代码:

function SuperType() {             this.property = true;             if (typeof this.SaySuperValue != "function") {                 SuperType.prototype.SaySuperValue = function () {                     return this.property;                 }             }         }          function SubType() {             this.SubProperty = false;             SuperType.call(this);           }         function inheritPrototype(subType, superType) {             var prototype = Object.create(superType.prototype); //创建超类原型的副本             prototype.constructor = subType;  //弥补 子类修改原型指向 所造成的默认构造函数的丢失             subType.prototype = prototype;    //修改子类 原型         }          inheritPrototype(SubType, SuperType);         SubType.prototype.SaySubValue = function () {             return this.SubProperty;         }          var in1 = new SubType();         alert(in1.SaySuperValue());  //true         alert(in1.SaySubValue());  //false

上述代码和 组合继承的不同之处在于, 修改子类的原型时,不是 用的超类的实例,而是超类的原型副本.

而实现这个的就是  inheritPrototype()函数,从而,只调用了一次 超类的构造函数, 也因此避免 了  在子类原型 上创建的一些不必要的

,多余的属性. 于此同时,原型链还能保持不变.  因为可以正常使用 instanceof.

两节的小结:

工厂模式: 使用简单的函数创建对象,为对象添加属性和方法,然后返回对象.

构造函数模式:可以创建自定义引用类型,可以向内置对象一样使用 new操作符,缺点是每个成员不能复用.

原型模式: 使用构造函数的 prototype属性来指定那些可以用共享的属性和方法.

javascript主用是通过

原型链实现继承.缺点 每个子类的实例共享属性和方法,没有各自独立的属性.

借用构造函数继承. 可以实现子类的实例有各自独有的属性,但是 子类的方法不能得到复用

组合继承.  调用了两次超类的构造函数

寄生式继承.  缺点同借用构造函数

寄生组合是继承.  比较理想的继承方式.

 

 

在面向对象语言中,继承有两种形式:1.接口继承; 2.实现继承。在js中只有实现继承。

利用原型链来实现继承,基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。

代码如下:

Java代码  收藏代码
  1.   function superClass (){  
  2.         this.property1 = true;  
  3.     }    
  4.     superClass.prototype.method1 = function (){  
  5.         console.log("superClass method1");  
  6.     }  
  7.     superClass.prototype.method2 = function (){  
  8.         console.log("superClass method2");  
  9.     }  
Java代码  收藏代码
  1.     function subClass (){  
  2.         this.property2 = false;  
  3.     }  
  4.     subClass.prototype = new superClass();  
  5.     subClass.prototype.method1 = function (){  
  6.         console.log("subClass method1");  
  7.     }  
  8.     subClass.prototype.method3 = function () {  
  9.         console.log("subClass method3");  
  10.     }  
Java代码  收藏代码
  1.     var superclass = new superClass ();  
  2.     superclass.method1();                        //superClass method1  
  3.     superclass.method2();                        //superClass method2  
  4.     var subclass = new subClass ();  
  5.     subclass.method1();                          //subClass method1  
  6.     subclass.method2();                          //superClass method2  
  7.     subclass.method3();                          //subClass method3   

 

上面的例子中subClass继承了superClass中的method1 和method2 两个方法,并且重写了method1方法。增加了自己的method3 方法。

 

subClass 继承的时候其实是重写了原型对象,使prototype指向了superClass 的一个实例。

 

原来存在与superclass的实例中的属性和方法,现在也存在与subClass.prototype中。
 即:原来在superClass中为实例属性和方法,现在为subClass中为原型属性和方法。

 

他们的关系如图:

 

上面的代码我们并没有使用 subClass 默认提供的原型,而是给它换成了一个新的原型。这个原型就是 superClass 的一个实例。

于是新的原型就有了 superClass 的实例所拥有的所有的属性和方法。而且内部还有一个指针指向了superClass 的原型。

最终的结果是:subclass 指向了 subClass 的原型,subClass 的原型指向了 superClass 的原型。 method2 方法还是存在于 superClass 方法中。但是 property1 存在于 subClass.prototype 中。这是因为 property1 是一个实例属性而 method2 为原型方法。

 

在 superClass 中的 method1 不能被 subclass 访问到,是因为原型的搜索机制导致的。

原型机制的搜索机制为:每当读取某个对象的属性或者方法的时候,都会先搜索实例中是否有该属性或者方法,如果没有继续搜索原型中的属性或者方法,如 果还是没有则继续向下搜索,直到 Object。所以 subClass.prototype 中的 method1 覆盖了 superClass.prototype 中的 method1 。

 

原型链的问题:

    在原型链中出现了一个问题,就是本来在父类中为实例属性和方法,在继承类中却变成了原型属性和方法。例如上面的property1。

    解决这个问题需要使用原型链和构造函数组合的办法,即组合继承或者伪经典继承。

 

 

 

 

posted on 2012-10-20 15:47  啊T  阅读(400)  评论(0编辑  收藏  举报