JS 继承-(一)原型链继承、构造函数继承、组合继承

前言

都说了 JS 是面向对象的语言啦,单线程。
拥抱面向对象
继承、封装、多态

原型链继承

  • 核心:一个子类函数的 prototype 指向父类函数的实例child.prototype = new parent()
   function foo1() {
       this.name = '小明';
   }
   function foo3() {
       this.size = 'max'
   }
   
   foo3.prototype = new foo1(); //1.原型链继承
   
   var test3 = new foo3()//2.原型链继承

   //原型链继承的测试
   console.log('test3 instanceof foo3: ', test3 instanceof foo3); //true
   console.log('test3.__proto__ === foo3.prototype: ', test3.__proto__ === foo3.prototype); //true

   //???
   console.log('foo3.prototype.constructor: ', foo3.prototype.constructor); //foo1 而不是 foo3? 因为foo3也是foo1的实例?
   console.log('test3.__proto__.constructor: ', test3.__proto__.constructor); //foo1 而不是 foo3?因为foo3也是foo1的实例?

   console.log('test3.__proto__: ', test3.__proto__)//foo1
   /*
   看来这就造成了原型链继承的缺点 , 任何一个实例修改与‘原型内属性同名’的属性 会造成其他变量也被污染了
   (那也分情况,如果属性是基本数据类型就不会污染,引用数据类型会污染)
   */
   console.log(' ');
   console.log('-----------------------------------');
   console.log(' ');
   //污染测试
   var test3_re_foo1 = new foo1()
   var test3_re_foo3 = new foo3()
   console.log('---------污染前----------');
   console.log('test3.name: ', test3.name); //小明
   console.log('test3_re_foo1.name: ', test3_re_foo1.name);//小明
   console.log('test3_re_foo3.name: ', test3_re_foo3.name);//小明

   test3.name = '小红'

   console.log('---------污染后----------');
   console.log('test3.name change: ', test3.name);//小红
   console.log('test3_re_foo1.name change: ', test3_re_foo1.name);//小明
   console.log('test3_re_foo3.name: ', test3_re_foo3.name);//小明

污染测试

function SuperType(){
    this.colors = ["red", "blue", "green"];
  }
  function SubType(){}
  
  SubType.prototype = new SuperType();
  
  var instance1 = new SubType();
  instance1.colors.push("black");
  console.log(instance1.colors); //"red,blue,green,black"
  
  
  var instance2 = new SubType(); 
  console.log(instance2.colors); //"red,blue,green,black"
  

构造函数继承

  • 核心:父类的构造函数在子类的构造函数里调用,使用 call 函数更改父类构造函数的 this 指向,帮忙为子类创建和父类相同的属性,

  • 实质1:并不是传统上的继承,更像是耍了点小聪明,与把父类的代码复制粘贴到子类无异(个人拙见)

  • 实质2:子类创建的实例没有链接到父类的原型,像是两条不同的主线(原型链),这也就造成了构造函数继承不像原型链继承那样,实例更改属性,会导致原型的属性也被污染,就像是一条主线,而且子任务和父任务是相互影响的

   
        //使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)
        function parent() {
            this.name = '小明';
        }
        function child() {
            parent.call(this); //1.构造函数继承
            this.sex = 'male'
        }
        var test2 = new child() //2.构造函数继承


        //构造函数继承的测试
        console.log('test2 instanceof child: ', test2 instanceof child); //true
        console.log('test2 instanceof parent: ', test2 instanceof parent); //false 
        /*
         说明原型链断了,test2的原型链上不包括parent,
         也不能说断了,只是test2的原型的构造函数foo2调用了另一个原型的构造函数,也就是parent,帮他赋值创建了一些东西,
         call那行代码与直接把parent里面的代码复制粘贴到child里面一样
         差不多是
        神秘的原型   神秘的原型
           |            |
           |            |
         parent       child
                        |
                        |
                      test2
         */

        console.log('test2.__proto__ === child.prototype: ', test2.__proto__ === child.prototype); //true


        console.log('child.prototype.constructor: ', child.prototype.constructor); //child 
        console.log('test2.__proto__.constructor: ', test2.__proto__.constructor); //child 
        console.log(' test2.__proto__.constructor === parent: ', test2.__proto__.constructor === parent); //false

        console.log('test2.__proto__: ', test2.__proto__)//{constructor: ƒ}

        /*
        构造函数继承的实质是改变了**改变this的指向**,
        parent的实例 --> 改为指向child的实例。导致 parent的实例的属性挂在到了child的实例上,这就实现了继承。
        */
        // 因为构造函数继承的实质,导致了使用构造函数继承,某一个实例修改变量时,其他原型的实例并不会被污染的结果
        // 但因为他是子函数(或者子类)的实例,父类里用点加入了新方法,这个他是不能更新继承到的
        // 例子
        parent.age = 13 // 如果你以为这样写,原型就加入新属性了是不对的,因为函数也是一个对象,在这个函数的原型链上也算是一个独立的个体,这个给函数加了东西不能给原型加上新属性,确定吗?看下面代码证明一下
        var test_parent = new parent()
        console.log('test_parent: ', test_parent.age); //undefined 验证了上面的说法,parent在他自个的原型链上是一个独立的个体
        
        parent.prototype.age = 13 // 给原型加上
        var test_parent_2 = new parent()
        console.log('test_parent: ', test_parent.age) // 13
        console.log('test_parent_2: ', test_parent_2); // age 在原型里
        console.log('test_parent_2.age: ', test_parent_2.age); // 13

        console.log('test2.age: ', test2.age); //undefined 为什么这里没有报引用类型错误,可能是对象的原因吧 看下面的代码
        var obj = { age:13}
        console.log('obj.sex: ', obj.sex);// undefined 确实 是对象的原因

        child.prototype.age = 15
        console.log('test2.age: ', test2.age);//15

组合继承:构造函数+原型链继承

  • 实质:满足了你希望的最好效果,实例加属性就只有这个实例加了,原型和其他实例都不会收到影响。原型加属性,就能同步更改所有实例
  • 原型不受实例影响的原因:因为构造函数继承的实质,导致了使用构造函数继承,覆写了子类原型中的属性(子类原型是父类实例化的属性),某一个实例修改变量时,其他原型的实例并不会被污染的结果
  • 缺点:让父亲Parent的构造方法执行了两次。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>组合继承</title>
</head>
<body>
    <script>
    /*
        组合方式实现继承:构造函数、原型链
        类的首字母大写
         
     Object
       |
       |
     Parent
       |
       |
     Child
       |
       |
 child1、child2
    
    */
    function Parent() {
        this.name = 'Parent 的属性';
        this.arr = [1, 2, 3];
    }

    function Child() {
        Parent.call(this); //【重要1】执行 parent方法
        this.type = 'Child 的属性';
    }
    Child.prototype = new Parent(); //【重要2】第二次执行parent方法

    console.log('child和parent的原型是否相同',Child.prototype===Parent.prototype); //false
    console.log('child的原型',Child.prototype.constructor); //parent
    console.log('parent的原型',Parent.prototype); //Object

    var child1= new Child();
    var child2 = new Child();
    
    //他是child的实例,原型指向parent的实例
    // 而parent的原型就是Object,原型的构造函数是parent,
    // 改变child的原型会引起他们的改变,改变parent的原型也会引起他们的改变
   
    console.log('child1: ', child1);
    console.log('child2: ', child2);

    console.log(' ');
    Child.prototype.sex = 'male'
    console.log('更改Child的原型');
    console.log('child1.sex: ', child1.sex);// male
    console.log('child2.sex: ', child2.sex);// male

    console.log(' ');
    Parent.prototype.love = 'female';
    console.log('更改Parent的原型');
    console.log('child1.love: ', child1.love); // female
    console.log('child2.love: ', child2.love); // female

    console.log(' ');
    console.log('-----------------------------------');
    console.log(' ');

    console.log('基本数据类型污染测试');
    child1.name = 'child1'
    console.log('child1.name: ', child1.name);// child1
    console.log('child2.name: ', child2.name);// 'Parent 的属性'

    console.log('查看原型的变化'); // 无变化
    console.log('child1.__proto__: ', child1.__proto__.name);// 'Parent 的属性'
    console.log('child2.__proto__: ', child2.__proto__.name);// 'Parent 的属性'
    
    console.log(' ');

    console.log('引用数据类型污染测试'); // 无变化
    child1.arr.push(4)
    console.log('child1.arr: ', child1.arr);// 1,2,3,4
    console.log('child2.arr: ', child2.arr);// 1,2,3

    console.log('查看原型的变化'); // 无变化
    console.log('child1.__proto__: ', child1.__proto__.arr);// 1,2,3
    console.log('child2.__proto__: ', child2.__proto__.arr);// 1,2,3
    </script>
</body>
</html>

参考文章:JavaScript常用八种继承方案

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值