JavaScript原型链实现继承

学习记录js高级程序设计

原型链

基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

假如让原型对象等于另一个类型的实例,此时原型对象就会包含一个指向另一个原型的指针,另一个原型中也会包含一个指向另一个构造函数的指针,层层递进。
这就是原型链的基本概念

基本模式

    function SuperType(){
        this.property=true;
    }
    SuperType.prototype.getSuperValue=function(){
        return this.property;
    };
    function SubType(){
        this.subproperty=false;
    }
    //继承了SuperType
    SubType.prototype=new SuperType();

    SubType.prototype.getSubValue=function(){
        return this.subproperty;
    };
    var instance=new SubType();
    console.log(instance.getSuperValue());  //true

在上面代码中,我们将SubType的原型替换为了新原型即SuperType的实例。
新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,其内部还有一个指针,指向了SuperType的原型
最终结果为:instance指向SubType的原型,SubType的原型又指向SuperType的原型。
GetSuperValue方法仍然在SuperType.prototype中,但property则位于SubType.prototype中,因为property是一个实例属性,而GetSuperValue是一个原型方法
SubType.prototype现在是SuperType的实例,那么property就位于该实例中。
instance.constructor现在指向SuperType,因为原来的SubType.prototype中的constructor被重写了,实际上不是重写,而是指向了SuperType的原型,而这个原型对象constructor属性指向SuperType

通过实现原型链,本质上扩展了原型搜索机制

默认的原型

所有的原型默认都继承了Object,而这个继承也是通过原型链实现的。

SubType继承了SuperType,而SuperType继承了Object。

确定原型和实例的关系

instanceof操作符:只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就为true

    console.log(instance instanceof SuperType)      //true

isPrototypeOf()方法:同样只要原型链出现过的原型,都可以说是该原型链派生的实例的原型,该方法会返回true。

    console.log(Object.prototype.isPrototypeOf(instance));      //true

谨慎定义方法

子类型有时候需要重写父类中的方法,或添加某些方法。
但给原型添加方法的代码一定要放在替换原型的语句之后。

    function SuperType(){
        this.property=true;
    }
    SuperType.prototype.getSuperValue=function(){
        return this.property;
    };
    function SubType(){
        this.subproperty=false;
    }
    //继承了SuperType
    SubType.prototype=new SuperType();
    //添加新方法
    SubType.prototype.getSubValue=function(){
        return this.subproperty;
    };
    //重写父类中的方法
    SubType.prototype.getSuperValue=function(){
        return false;
    };
    var ins=new SubType();
    console.log(ins.getSuperValue());//false;

通过子类实例调用的是重写后的方法,但通过父类实例调用的是重写前的方法。

通过原型链实现继承时,不能使用对象字面量创建原型方法,这样会重写原型链

原型链的问题

引用类型的值的原型会被所有实例共享,也就是为什么在构造函数中定义属性,而不在原型中定义属性的原因

    function SuperType(){
        this.colors=["red","blue"];
    }
    function SubType(){
    }
    //继承了SuperType
    SubType.prototype=new SuperType();
    var ins1=new SubType();
    ins1.colors.push("black")   //"red,blue,black"
    var ins2=new SubType();
    console.log(ins2.colors);   //"red,blue,black"

SuperType构造函数定义了一个colors引用类型属性,SuperType的每个实例都会包含自己的colors数组,当SubType继承SuperType时,SubType.prototype就成了SuperType的一个实例,它有了自己的colors属性–SubType.prototype.colors属性一样。所有SubType实例共享这一个属性。

第二个问题:创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

借用构造函数

为解决原型中包含引用类型值的问题,引入了借用构造函数的技术。
即在子类型构造函数的内部调用超类型构造函数。通过使用apply()和call()方法可以在新创建的对象上执行构造函数

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

    var ins2=new SubType();     //"red,blue"

传递参数
相对原型链,借用构造函数有一个很大优势,即可以在子类型构造函数中向超类型构造函数传递参数

    function SuperType(name){
        this.name=name;
    }
    function SubType(){
        //继承SuperType,并传递参数
        SuperType.call(this,"nich");
        this.age=29;
    }
    var ins=new SubType();//"nich, 29"

为了确保超类型中的属性不会重写子类型的属性,可以在调用超类型构造函数后,在添加子类型定义的属性

借用构造函数的问题:方法都在构造函数中调用,没有了函数复用,在超类型原型定义的方法,子类不可见,结果所有类型都只能用构造函数模式

组合继承 最常用的继承模式

将原型链和借用构造函数组合到一起。思路是使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承

    function SuperType(name){
        this.name=name;
        this.colors=["red","blue"]
    }
    SuperType.prototype.sayName=function(){
        console.log(this.name);
    };
    function SubType(name,age){
        //继承属性
        SuperType.call(this,name);
        this.age=age;
    }
    //继承方法
    SubType.prototype=new SuperType();
    SubType.prototype.constructor=SubType;
    SubType.prototype.sayAge=function(){
        console.log(this.age);
    };
    var ins1=new SubType("nich",30);
    ins1.colors.push("black");  //"red,blue,black"
    ins1.sayName();//"nich"
    ins1.sayAge();//30

    var ins2=new SubType("greg",20);//"red,blue"
    ins2.sayName()//"greg"
    ins2.sayAge()//"20"

原型式继承

这种方式没有使用严格意义上的构造函数。借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型

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

本质上讲,object()对传入的对象执行了一次浅复制

    var person={
        name:"Nich",
        friends:["shelby","court"]
    };
    var anotherPerson=object(person);
    anotherPerson.name="greg";
    anotherPerson.friends.push("rob");

    var yetAnotherPerson=object(person);
    yetAnotherPerson.name="linda";
    yetAnotherPerson.friends.push("barbie");

    console.log(person.friends) //"shelby,court,rob,barbie

必须有一个对象可以作为另一个对象的基础,Person对象可以作为其他对象的基础,调用object后,新对象的原型就为person。person.friends被所有子类型实例共享。相当于创建了person对象的两个副本。

ECMAScript5新增Object.create()规范化了原型式继承,接收两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
只有一个参数时和object()方法的行为相同。
第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的,以这种方式定义的任何属性都会覆盖原型对象上的同名属性

    var person={
        name:"nich",
        friends:["shelby","court"]
    };
    var anp=Object.create(person,{
        name:{
            value:"greg"
        }
    });
    console.log(anp.name);//greg

在没有必要兴师动众创建构造函数,在两个对象需要保持类似的情况下,原型模式可以胜任。但是引用类型值的属性始终会共享相应的值。

寄生式继承

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

    function createAnother(original){
        var clone=object(original);     //通过调用函数创建一个新对象
        clone.sayHi=function(){         //以某种方式来增强对象
            console.log("hi");
        };
        return clone;                   //返回对象
    }
    var person={
        name:"nich",
        fri:["shelby","court"]
    };
    var anop=createAnother(person);
    anotherPerson.sayHi();          //"hi"

在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承是一种有用的模式。
object()函数不是必须的,任何能够返回新对象的函数都适用于此模式。
使用寄生继承为对象添加函数,会由于不能做到函数复用而降低效率。

寄生组合式继承 是引用类型最理想的继承范式

组合继承最大问题就是无论什么情况,都会调用两次超类型的构造函数,一次在创建子类型原型的时候,另一次是在子类型构造函数内部。

寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们需要的无非就是超类型原型的一个副本而已。本质就是用寄生式继承来继承超类型的原型,然后在将结果指定给子类型的原型。

    function inheritPrototype(subType,superType){
        var prototype=object(superType.prototype);  //创建对象
        prototype.constructor=subType;              //增强对象
        subType.prototype=prototype;                //指定对象
    }
    //先创建超类型的副本,为副本添加constructor属性,弥补因重写原型失去的默认的constructor属性,将创建的副本赋值给子类型的原型
    function SuperType(name){
        this.name=name;
        this.colors=["red","blue"];
    }
    SuperType.prototype.sayName=function(){
        console.log(this.name);
    };
    function SubType(name,age){
        SuperType.call(this,name);
        this.age=age;
    }
    inheritPrototype(SubType,SuperType);
    SubType.prototype.sayAge=function(){
        console.log(this.age);
    };
    //只调用了一次SuperType构造函数,避免了在SubType.prototype上面创建不必要的、多余的属性。

欢迎大家关注我的公众号 一起学习

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值