js面向对象之继承

239 阅读4分钟
引言

假如一个对象的原型指向的是另一个对象的实例,而这个实例也有自己的原型指向,如此层层递进,即为原型链。

  • 所有对象都默认继承了Object对象,如我们通常会用到的toStringvalueOf函数。
    function Person(){
        this.name="zhangsan";
        this.age=24;
    }
    
    //没自己指定原型索引指向时默认会有类似Person.prototype=Object的写法;
    
    Person.prototype.sayName=function(){
    console.log(this.name);
    }
    var person=new Person();
    console.log(person.valueOf());
    //打印结果
    Person { name: 'zhangsan', age: 24 }

原型链图:

顺着原型链最顶层的原型对象一定是Object对象

  • A对象的原型指向B对象的实例,则A对象的实例共享B对象的实例属性,太符合继承的设计理念
    function Person(){
        this.name="zhangsan";
        this.age=24;
    }
    Person.prototype.sayName=function(){
        return this.name;
    }
    function Student(score,personNo){
        this.score=score;
        this.personNo=personNo;
    }
    Student.prototype=new Person();
    var student1=new Student(25,1112);
    //与引用类型直接修改不同
    //不能直接用student1.name="lisi",这相当于直接创建了一个name值为lisi的实例变量,没对原型中的name进行修改
    Object.getPrototypeOf(student1).name="lisi";
    student1.personNo=6987
    console.log("personNo为:"+student1.personNo);
    var student2=new Student(25,1112);
    console.log(student2.name);
    console.log(student2.personNo);
    //打印结果
    personNo为:6987
    lisi
    1112

上面Person里面的属性基本数据类型,但如果是引用数据类型如Array,属性的通过方法修改时,不会创建的实例变量

    function Person(){
        this.name="oooo";
        this.frinds=["zhang","wang"]
    }
    Person.prototype.sayName=function(){
        return this.name;
    }
    function Student(score,personNo){
        this.score=score;
        this.personNo=personNo;
    }
    Student.prototype=new Person();
    var student1=new Student(25,1112);
    //直接修改
    student1.frinds.push("haha");
    student1.personNo=6987
    console.log("personNo为:"+student1.personNo);
    var student2=new Student(25,1112);
    student2.frinds.push("wwww");
    console.log(student2.name);
    console.log(student2.frinds.toString());
    //打印结果
    personNo为:6987
    oooo
    zhang,wang,haha,wwww

为了解决上述问题,提出了不同的解决思路

借用构造函数

SuperType当做一个函数调用,将它的属性用call函数与SubType绑定

   function SuperType(){
       this.colors=["red","blue"];
   }
   function SubTyPe(){
       SuperType.call(this);
   }
  var instance1=new SubTyPe();
  instance1.colors.push("black");
  console.log(instance1.colors.toString());
  var instance2=new SubTyPe();
  instance2.colors.push("white");
  console.log(instance2.colors.toString());
  //打印结果
  red,blue,black
  red,blue,white

问题:方法都在构造函数内部定义,函数的复用就无从谈起,且超类型原型中的方法,对子类型而言不可见。

组合继承

子类型A的原型指向超类型B实例C,同时在A的构造函数中调用B函数来覆盖A指向的原型C中的属性和方法,只让C原型里面的属性和方法有效

    function SuperType(name){
        this.name=name;
        this.colors=['red','blue'];
    }
    SuperType.prototype.sayName=function(){
        return 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(){
        return this.age;
    }
    var instance1=new SubType("张三",25);
    instance1.colors.push("black");
    console.log("insatnce1 "+instance1.sayName());
    var instance2=new SubType("李四",21);
    instance2.colors.push("brown");
    console.log(instance2.colors.toString());
    //打印结果
    insatnce1 张三
    red,blue,brown
原型式继承

基于已有的对象创建一个对象,还不必自定义对象类型

    function object(o){
        function F(){};
        F.prototype=o;
        return new F(); 
    }
    var person={
        name:"lisi",
        friend:["zhangsan","lisi"],
    }
    var copyPerson1=object(person);
    copyPerson1.friend.push("wanger");
    var copyPerson2=object(person);
    copyPerson2.friend.push("mazi");
    console.log(copyPerson2.friend.toString());
    //打印结果
    zhangsan,lisi,wanger,mazi

ES5通过新增Object.create()规范了原型式继承,可传入个参数----->1.用作新对象原型的对象 2.为新对象定义额外属性对象。------> 前者定义原型属性,后者定义实例属性.

    var person={
      name:"zhangsan",
      age:25,
      colors:["red","blue"],
    }
    var student1=Object.create(person,{socre:{enumerable:false,writable:true,value:25}});
    student1.socre=35;
    student1.colors.push("black");
    console.log("student1的分数:"+student1.socre);
    var student2=Object.create(person,{socre:{enumerable:false,writable:true,value:25}});
    student2.socre=45;
    student2.colors.push("brown");
    console.log("student2的分数:"+student2.socre);
    console.log("student2:"+student2.colors.toString());
    //打印结果
    student1的分数:35
    student2的分数:45
    student2:red,blue,black,brown
寄生式继承

利用原型式继承,并在构造函数内部对进行加强,感觉就是Object.create()传两个参数的具体写法。

     function Person(o){
       function F(){}
       F.prototype=o;
       return new F();
     }
     function Student(original){
       var person=Person(original);
       person.sayName=function(){
         console.log(this.name);
       }
       return person;
     }
      var object={
        name:"zhangsan",
        age:25,
      }
      var student=Student(object);
      student.sayName();
      //打印结果
      zhangsan
寄生组合式继承

组合式继承中,构造函数A继承构造函数B,将A的原型索引指向B的实例,在A中调用了B来覆盖B中的实例属性------>所以我们真正需要的是,将A的原型索引指向B的实例的原型,再调用B函数创建实例属性

    //超类型
    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;
    }
    //原型式继承
    function clone(o){
      function F(){}
      F.prototype=o;
      return new F();
    }
    //copy超类型的原型
    function inheritPrototype(superType,subType){
      var object=clone(superType.prototype);
      object.contructor=subType;
      subType.prototype=object;
    }
    inheritPrototype(SuperType,SubType);
    var subType=new SuperType("zhangsan",25);
    subType.sayName();
    //打印结果
    zhangsan