Js面向对象漫谈(2) 继承--原型链,借用构造函数,组合式,寄生式, 寄生组合式

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主用是通过

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

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

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

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

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

posted @ 2012-06-05 10:01  高捍得  阅读(580)  评论(0编辑  收藏  举报