面向对象-原型链继承

151 阅读4分钟

概念解析:

三个类

  1. 超类型
  2. 父类型
  3. 子类型

继承:就是让子类拥有父类的资源

问题:子类默认无法访问父类的任何对象

解决方案:修改原型的指向

继承的方式:

  1. 原型链的继承
  2. 借用构造函数继承
  3. 组合继承
  4. 原型式继承
  5. 寄生式继承
  6. 寄生式组合继承
  7. 拷贝属性继承

继承的意义:

  1. 减少代码冗余
  2. 方便统一操作
  3. 弊端:耦合性比较强 ,比如说其中一环出了问题,整个链条都瘫痪掉了

每个函数都能构建出一个对象,这个对象内部有个属性指向着这个函数的原型对象

原型对象本质上也是一个对象,也是由另一个构造函数构造出来的,也指向那个构造函数的原型对象

以上,形成一个链式的结构,就称为原型链

原型链继承:
var arr = [1,2,3];

    console.log(arr.constructor.name); //Array
    console.log(arr);
    
    
    console.log(arr.__proto__.constructor.name); //Array
    
    console.log(Array.__proto__.constructor.name); //Function

    console.log(Function.__proto__.constructor.name); //Function 继承顶层

    console.log(Function.prototype.__proto__.constructor.name); //Object
    
    console.log(Object.prototype.__proto__); //null

    console.log(Object.__proto__.constructor.name); //Function
    
    console.log(Array.prototype.__proto__.constructor.name); //Object

一、 让子类拥有父类的资源

//构造函数Person
    function Person() {
        this.name = '张三';
        this.pets = ['小煤球','花花'];
    }

    Person.prototype.run = function () {
        console.log('跑');
    }

    //构造函数Student
    function Student() {
        this.num = 'aaaa.com';
    }

    var stu = new Student();
    //两个函数没有关系
    console.log(stu);
    console.log(stu.num);
    console.log(stu.name);
    console.log(stu.pets);

问题:子类默认无法访问父类的任何对象

解决方案:修改原型的指向比较方便

二、修改原型的指向

//构造函数Person
    function Person() {
        this.name = '张三';
        this.pets = ['小煤球','花花'];
    }

    Person.prototype.run = function () {
        console.log('跑');
    }

    //构造函数Student
    function Student() {
        this.num = 'aaaa.com';
    }

    //让Student的原型对象指向Person的原型对象
    Student.prototype = Person.prototype;

    var stu = new Student();
    //两个函数没有关系
    console.log(stu);
    console.log(stu.num);

    stu.run();

问题:类型问题,指针指向父类构造函数

解决方案:修复constructor指针即可

三、 构造父类的实例,并设置为子类的原型对象

//构造函数Person 父类
    function Person() {
        this.name = '张三';
        this.pets = ['小煤球','花花'];
    }

    Person.prototype.run = function () {
        console.log('跑');
    }

    //构造函数Student 子类
    function Student() {
        this.num = 'aaaa.com';
    }

    //1.构造父类的实例
    var p = new Person();    
    //2.并设置为子类的原型对象
    Student.prototype = p;
    //3.修复constructor指针
    Student.prototype.constructor = Student;

    var stu = new Student();
    //两个函数没有关系
    console.log(stu);
    console.log(stu.num);

    stu.run();
    
    console.log(stu.name);
    console.log(stu.pets);

    console.log(stu.constructor.name); //Person

问题:无法访问到父类的对象属性

思路:怎样才能拥有父类的实例属性和原型属性? 构造父类的实例,只要在原型对象上,肯定都会被共享

解决方案:添加到对象自己身上,在构造函数内部添加

优化解决方案:可以直接调用父类构造函数,但是需要修改this的指向

再优化:注意覆盖关系,如果产生重名,应该子类覆盖父类,借助构造函数继承,在子构造函数内部调用父构造函数

四、借助构造函数继承,在子构造函数内部调用父构造函数

//构造函数Person 父类
    function Person() {
        this.name = '张三';
        this.pets = ['小煤球','花花'];
    }

    Person.prototype.run = function () {
        console.log('跑');
    }

    //构造函数Student 子类
    function Student() {
        //借助构造函数继承,在子构造函数内部调用父构造函数
        //问题:父类构造函数的参数无法修改
        //注意:从此处开始已经覆盖了原型链继承和借助构造函数继承,称之为 '组合继承' = 原型链 + 借助构造函数
        Person.call(this);

        this.num = 'aaaa.com';
        //尝试1:在构造函数内部添加,把父类的属性复制到子类身上,对应的实例化的对象也会有这些属性
        // this.name = '张三';
        // this.pets = ['小煤球','花花'];

        //尝试2:直接调用父类构造函数,但是需要修改this的指向
        // Person.call(this);
    }

    //1.构造父类的实例
    var p = new Person();    
    //2.并设置为子类的原型对象
    Student.prototype = p;
    //3.修复constructor指针
    Student.prototype.constructor = Student;

    var stu = new Student();
    var stu2 = new Student();

问题:父类构造函数的参数无法修改

注意:从此处开始已经覆盖了原型链继承和借助构造函数继承,称之为 '组合继承' = 原型链 + 借助构造函数

解决方案:父类构造函数需要设置接收可变参数,子类构造函数在调用父类构造函数的时候,传递参数即可

解决方案:寄生组合型继承

五、完美继承:寄生组合型继承

 //构造函数Person 父类
    function Person(name,pts) {
        this.name = name;
        this.pets = pts;
    }

    Person.prototype.run = function () {
        console.log('跑');
    }

    //构造函数Student 子类  //借助构造函数继承
    function Student(num,name,pts) {
        //注意:一定要放在最前面
        Person.call(this,name,pts);

        this.num = num;

    }

   //1.寄生式继承
   function Temp() {} //空的继承函数,作用就是用来寄生
   //2.找到需要继承的原型对象
   Temp.prototype = Person.prototype;
   //3.让子类构造函数的原型对象为寄生构造函数的实例化对象
   var stuPrototype = new Temp();
   Student.prototype = stuPrototype;
   //4.让寄生构造函数的原型对象的真实类型指向子类构造函数
   stuPrototype.constructor = Student;
   
    
</script>
<script>
    var stu = new Student('001','张三',['小花']);

    var stu2 = new Student('002','李四',['小猫']);

    console.log(stu);
    console.log(stu2);
    console.log(new Person());
    
    
</script>

总结:

针对于父类构造函数的实例属性:

使用借助构造函数继承的方式,在子构造函数中,调用父构造函数

!注意:

修改指针,放在子类构造函数内部最前面

针对于父类构造函数的原型对象属性:

  1. 原型链继承
  2. 寄生式组合继承