面向对象-原型链继承(中)

这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战

前言

上期我们大概讲了一些基本的东西,比如什么是继承,原型链的检索规则,以及对继承举了2个例子啊

面向对象-继承(上)

今天我们继续举例,借助代码图解进一步学习,然后再进行一个小总结

一、继承-原型链继承-2

这里我们解决昨天遗留的那个问题先。

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

换句话来说就是怎样才能拥有父类的实例属性和原型属性?

解决方案:构造父类的实例,并设置为子类的原型对象

    <script>
        /**
         * 构造函数Person
         * @constructor
         */
        function Person() {
            this.name = 'Jack';
            this.lore = ['html', 'css'];
        }

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


        /**
         * 构造函数Student
         * @constructor
         */
        function Student() {
            this.num = 'Tom';
        }

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

        var stu = new Student();
        console.log(stu); // Student {num: "Tom"}

        console.log(stu.num); // Tom
        stu.run(); // 跑
        console.log(stu.name); // Jack
        console.log(stu.lore); // ["html", "css"]

        console.log(stu.constructor.name); // Person
    </script>
复制代码

图解

2.png

问题:类型问题

二、继承-原型链继承-3

解决问题:修复constructor指针即可

    <script>
        /**
         * 构造函数Person
         * @constructor
         */
        function Person() {
           this.name = 'Jack';
           this.lore = ['html', 'css'];
        }
    
        Person.prototype.run = function () {
            console.log('跑');
        };
    
    
        /**
         * 构造函数Student
         * @constructor
         */
        function Student() {
           this.num = 'Tom';
        }
    
        // 1. 构造父类的实例
        var p = new Person();
        // 2. 并设置为子类的原型对象
        Student.prototype = p;
        // 3.修复constructor指针即可
        Student.prototype.constructor = Student;
    
        var stu = new Student();
  
        console.log(stu); // Student {num: "Tom"}

        console.log(stu.num); // Tom
        stu.run(); // 跑
        console.log(stu.name); // Jack
        console.log(stu.lore); // ["html", "css"]

        console.log(stu.constructor.name); // Student
    
    </script>
复制代码

图解

3.png

问题:继承过来的实例属性, 如果是引用类型, 会被多个子类的实例共享

注意:到此为止,原型链继承已经结束

三、继承-原型链继承-4

思路: 只要在原型对象上,肯定都会被共享。

解决方案

1. 添加到对象自己身上

(1) 在创建过对象后添加(复用性差,冗余度高,稳定性差,不采用),不推荐

(2) 在构造函数内部添加

function Stu () {
   this.num = '20210819';

   // 必须跟父类写的一模一样
   this.name = 'Rose';
   this.lore = ['js', 'jquery'];
}
复制代码

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

 function Stu() {
    this.num = '20210819';

    // 必须跟父类写的一模一样
    // this.name = 'Rose';
    // this.lore = ['js', 'jquery'];

    Person.call(this);
}
复制代码

再优化: 注意覆盖关系,如果产生重名, 应该, 子类覆盖父类

 function Stu() {
    Person.call(this);
    this.num = '20210819';

    // 必须跟父类写的一模一样
    // this.name = 'Rose';
    // this.lore = ['js', 'jquery'];

}
复制代码

图解

Student函数就已经获得了Person函数namelore了。

4.png

概念:“借用构造函数继承”,在子构造函数内部,调用父构造函数

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

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

四、继承-原型链继承-5

解决方案

  1. 父类构造函数, 需要设置接收可变参数
  2. 子类构造函数在调用父类构造函数的时候, 传递参数即可
    <script>
        /**
         * 构造函数Person
         * @constructor
         */
        function Person(name, lore) {
            this.name = name;
            this.lore = lore;
        }

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


        /**
         * 构造函数Student
         * @constructor
         */
        function Student(num, name, lore) {
            // 注意: 一定要放在最前面
            Person.call(this, name, lore);
            this.num = num;
        }

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

        var stu = new Student('001', '张三', ['html']);
        var stu2 = new Student('002', '李四', ['css']);
        console.log(stu);
        console.log(stu2);
    </script>
复制代码

image.png

问题: 父类属性重复,实例上有一份,原型对象上有一份。(如下图)

image.png

五、继承-原型链继承-6

思路

1. 理解为什么会有两份?

因为调用了两次父类构造函数

  • 1次,在子类构造函数内部
  • 2次,创建子类构造函数原型对象时

分析

两次调用的意义?

  • 1次, 在子类构造函数内部
    • 为了获取父类的实例属性
  • 2次, 创建子类构造函数原型对象时
    • 为了获取父类的实例属性
    • 为了获取实例的原型对象属性

结论: 想办法, 设置子类构造函数的原型对象时, 只要父类构造函数的原型对象属性

解决方案

方案1:

修改子类构造函数的原型对象指针, 为父类构造函数的原型对象

问题:

  • 共享原型对象
  • 容易引发冲突
  • 无法判定类型

不采用!!!

方案2:

思路:

能不能修改子类构造函数的原型对象指针, 为一个对象

这个对象, 只能访问到父类原型对象的属性/方法?

解决方案

  1. 拷贝父类构造函数的原型对象
    • 注意: 此处是指拷贝原型对象的内容, 不是整个地址过来
    • 后面会讲解如何拷贝, 到时自行实现这块
  2. 借助父类构造函数的原型对象, 创建出来一个空对象实例

图解

概念

1. 原型式继承

借助原型,然后基于已有的对象, 创建出新对象;同时不需要创建自定义类型

核心

function createObjWithObj(obj) {
    function Tmp() {}
    Tmp.prototype = obj;
    var o = new Tmp();
    return o;
}
复制代码

系统实现
Object.create

1. 作用

  • 创建对象,并且设置该对象的原型对象为传递过来的参数
  • 兼容ES5,IE8不支持

2. 简单用法

var obj = {name:"sz"};
var obj2 = Object.create(obj);
        // 创建了一个空对象obj2
        // 把obj, 作为obj2的原型对象
复制代码

3. 兼容处理

if(typeof Object.create == "function") {
    var o = Object.create(obj);
} else {
    Object.create = function(){
        function F(){};
        F.prototype = obj;
        var o = new F();
    }
}
复制代码

2. 寄生式继承

在原型式基础上增强这个对象,所谓增加, 就是指, 再次给这个对象增加一些属性或者方法

核心

function createNewObjWithObj(obj) {
    // 1. 通过另外一个对象创建出一个新对象
    var o = createObjWithObj(obj);
    // 2. 增加对象
    o.name = 'sz';
    o.age = 18;
    // 3. 返回对象
    return o;
}
复制代码

六、继承-原型链继承-总结

1. 针对于父类构造函数的实例属性
使用借助构造函数继承的方式,在子构造函数中, 调用父构造函数

  • 注意修改this指针
  • 注意调用顺序,在子类构造函数内部最前

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

  1. 原型链继承
  2. 寄生式继承方式

3. 图解

5.png

七、结语

到这里我们就结束了继承的知识,明天我们搞一些练习巩固巩固哈,还有一个之前我们说到的拷贝属性,也给他整整。

欢迎前往专栏阅读其他相关的文章:JavaScript版块知识

码字画图不易,如果觉得对你有帮助,觉得还不错的话,欢迎点赞收藏~

当然由于是个人整理,难免会出现纰漏,欢迎留言反馈。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享