面向对象编程二之继承

146 阅读9分钟

今日阳光明媚,今日多云转晴,我的心情从晴天一下子就多云啦,为什么呢这是,因为我找到我姥爷啦... 。鸭蛋不差钱上的经典语录,我还是以一个欢快的语句入场。在这两章一直在讲面向对象面向对象,也不知道面向了个啥,最后没有面着对象只能面着自己啦哈哈哈,虽然现实中没面成也可能你抓不住身边的目标,但是js里边的对象可是只要你想就可以抓的住的,这可是稳扎稳打这波绝对不亏的啊。穿过西风与山河,安知甜与乐。毕竟这是学习的天地,这里只有未来的梦想和你想成为的那个人,还有诗和远方。今天我们就来讲讲面向对象中的继承吧,也不知道js为什么要从对象中继承,不是应该从老爹继承嘛,哈哈哈,管它呢先码了再说,还是那句话,本人菜鸟望大佬们轻喷。

继承

大家应该都知道OO语言中的最为人津津乐道的东西就是继承了吧,不服来辩哈哈哈,那么今天就来看看js是怎么实现继承的呢?

  1. 原型链

    • 原型链

      简单回顾一下构造函数、原型、实例的关系:每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。如果让原型对象等于另一个类型的实例,那此时原型对象将包含一个指向另一个原型的指针,相应的另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例呢,那么上述关系依然成立。那这就好玩了呀,就像你让你条蛇咬住另一条蛇的尾巴,下一条又咬住另一条的尾巴....,那这就成了啊,原型链。先看看代码和图吧,毕竟oppoR50000s 让你的思路更清晰 哈哈哈。

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

      要注意此时的instance.constructor 现在指向的是SuperType ,这是因为原来SubType.prototype中的constructor被重写了的缘故。

      调用instance.getSuperValue()会经历三个搜索步骤:1)搜索实例;2)搜索SubType.prototype; 3)搜索SuperType.prototype,最后这一步菜找到该方法。就是一层一层往上找呗。

    • 默认原型

      别忘了咱们所有的引用类型都继承了Object,在使用所有的引用类型时,不是一般都有什么tostring方法、valueOf方法,那它们哪里来的啊,不可能和孙悟空一样凭空一样吧?哦不,不是凭空是仙石孕育而生吧??哈哈哈那是不存在的吧,他们都是通过继承而来,这个继承也是通过原型链实现的,所以记住所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也是所有自定义类型都会继承tostring()、valueOf()的根本原因。

    • 确定原型和实力的关系

      如何确定原型和实例之间的关系,上边的原型链我们也看到了,多乱的关系啊,那不得有个方法能确定确定实例和原型的关系嘛

      • instanceof 可以测试实例原型链中出现过的构造函数,结果就会返回true。由于原型链的关系,我们可以说instance是Object 、SuperType或者SubType中任何类型的实例

        alert(instance instanceof Object);  //true
        alert(instance instanceof SuperType); // true
        alert(instance instanceof SubType); //true
        
      • isPrototypeOf()方法,同样的只要原型链中出现过的原型,都可以说是该原型链所派生的实例的原型

        alert(Object.prototype.isPrototypeOf(instance)); // true
        alert(SuperType.prototype.isPrototypeOf(instance)); // true
        alert(SubType.prototype.isPrototypeOf(instance)); // true
        
    • 谨慎定义方法

      子类型有时候需要覆盖超类中的某个方法,或者需要添加超类型中不存在的方法。但不管怎么样,给原型添加方法的代码一定要放在替换原型的语句之后

      function SuperType (){
      	this.property =true
      }
      SuperType.prototype.getSuperValue = function (){
      	return this.property;
      }
      SubType.prototype =  new SuperType();
      // 添加新的方法
      SubType.prototype.getSubValue = function(){
      	return this.subproperty;
      }
      // 重写超类型中的方法
      SubType.prototype.getSuperValue= function(){
      	return false
      };
      var instance = new SubType();
      alert(instance.getSuperValue());  // false
      

      *注意问题:*在通过原型链实现继承的时候,不能使用对象自变量(重新写了)创建原型方法。因为这样就重写了原型链,此时原型链就会被切断,两个类之间也就没有什么关系了

    • 原型链问题

      • 原型链虽然很强大,可以用来实现继承,但是也存在问题,不存在问题怎么会有更好的出现呢?是吧?最最主要的就是太能吃哈哈哈,开玩笑的啊 ,最主要的问题就是包含引用值的原型属性会被所有实例共享;原型链中一个原型变成另一个类型的实例,于是也就顺理成章的实现了现在的原型的属性啦

        function SuperType(){
          this.colors = ["red","blue","green"];
        }
        function SubType(){
        }
        SubType.prototype = new SuperType();
        var instance1 = new SubType();
        instance1.colors.push("black");
        alert(instance1.colors); // red,blue,green, black
        var instacne2 = new Subtype();
        alert(instance2.colors); // red,blue,green, black
        
      • 创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上应该说没有办法在不影响所有对象实例的情况下,给超类传递参数。

  2. 借用构造函数

    • 这种技术的主要思想非常简单,就是在子类型构造函数的内部调用超类型构造函数。函数只不过是再特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数,代码借调了超类型的构造函数,通过call()方法(或apply()方法也可以)实际就是在新创建的SubType的实例环境中调用了SuperType构造函数。这样在新的SubType对象上执行SuperType()函数中定义的所有对象初始化代码

      function SuperType(){
      	this.colors= ["red","blue","green"];
      }
      function SubType(){
        SuperType.call(this);
      }
      var instance1 = new SubType();
      instance1.colors.push("black");
      alert(instance1.colors); // red,blue,green, black
      var instacne2 = new Subtype();
      alert(instance2.colors); // red,blue,green
      
    • 传递参数

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

      function SuperType(name){
        this.name = name;
      }
      function SubType(){
        // 继承了SuperType,同时还传递了参数
        SuperType.call(this,"Nicholas");
        //实例属性
        this.age =29;
      }
      var instance = new SubType();
      alert(instance.name);  // "Nicholas"
      alert(instance.age);    // 29
      
    • 该模式存在的问题

      该模式存在的问题其实和构造函数存在的问题一样,方法都是在构造函数中定义,因此函数复用就无从谈起来,因为在每次进行new 实例的时候 就相当于拷贝了一份构造函数的方法,方法多了那内存也就相当大了。

  3. 组合继承

    • 它来了它来了,它带着绝招走来了,组合继承是当前最常用的继承方式,指的就是将原型链和借用构造函数的技术组合到一起。思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有自己的属性。

      function SuperType(name){
        this.name = name;
        this.colors= ["red","blue","green"];
      }
      SuperType.protptype.sayName = function (){
        alert(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(){
        alert(this.age);
      }
      var instance1 = new SubType("Nicholas",29);
      instance1.colors.push("black");
      alert(instance1.colors); // red,blue,green, black
      instance1.sayName();  //Nicholas
      instance1.sayAge();  // 29
      
      var instance2 = new SubType("Greg",27);
      alert(instance2.colors);// red,blue,green
      instance2.sayName(); //Greg
      instance2.sayAge();  //27
      
  4. 原型式继承

    • 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了可以随意添加属性的实例或对象。object.create()就是这个原理。

      var person = {
          name:"Nicholas",
          friends:["Shelby","Court","Vant"]
      }
      function object(obj){
          function F(){}
          F.prototype=obj;
          return new F();
      }
      var sup1=object(person);
      
    • 优点:类似复制一个对象,用函数来包装。

    • 缺点:1.所有实例都会继承原型上的属性。2.无法实现复用。(新实例的属性都是后面添加)

  5. 寄生式继承

    • 给原型式继承外面套个壳子。

      function object(obj){
        function F(){};
        F.prototype = obj; // 继承了传入的参数
        return new F();  // 返回函数对象
      }
      var sup1=object(person);
      // 以上是原型式继承,给原型式继承在涛哥壳子传递参数
      function subobject(obj){
        var sub = object(obj);
        sub.sayHi= function (){
          alert("hi");
        }
      	return sub
      }
      var sup2 = subobject(person);
      
    • 优点: 没有创建自定义类型,因为只是套了个壳子返回对象,这个函数就顺理成章的创建了新的对象

    • 缺点: 没用到原型,无法复用

  6. 寄生组合式继承

    • 前边说过组合继承是javascript中最常用的继承模式,不过它也有它的缺点的,毕竟嘛人无完人是吧?组合继承最大的问题就在于无论什么情况下,都会调用两次超类型的构造函数:依次是在创建子类型原型的时候,另一次是在子类型构造函数内,造成的结果就是有两组的name和colors属性一组是在实例上一组是在SubType的原型中。

      function SuperType(name){
        this.name =name;
        this.colors =["red","blue","green"]
      }
      SuperType.prototype.sayName = function (){
        alert(this.name)
      }
      function SubType(name,age){
        SuperType.call(this,name); // 第二次调用SuperType()
        this.age = age
      }
      SubType.prototype =new SuperType();// 第一次调用
      SubType.protptype.construtor = SubType;
      SubType.protptype.sayAge = function(){
        alert(this.age);
      }
      

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

      function inheritPrototype(subType,superType){
        var prototype = object(superType.prototype); // 创建对象
        prototype.constructor =subType; // 增强对象
        subType.prototype = prototype;  // 指定对象
      }
      

      在函数内部,第一步创建超类型原型的一个副本,第二步是为创建的副本添加constructor属性,从而弥补因重写原型而失去默认的construtor属性,最后一步,将创建的对象(副本),赋值给子类型的原型。

    这周事情比较多,趁着等着后台做接口的功夫,赶紧把未完成的东西写完,接口大佬还在睡觉,咱也不敢打搅哈哈哈,认真且怂,终于搞定了js对象,虽然感觉很是晦涩难懂,但还是啃了这块难啃的骨头,之后的知识应该是顺风顺水了吧,想多了哈哈哈,一想到乱七八糟的知识点怎么会那么容易,反正不管怎么样,盘它就完了。