zoukankan      html  css  js  c++  java
  • 总结继承的几种方式

    简单总结继承的几种方式

    JavaScript作为一门弱类型的语言,本着精简的原则,它取消了类的概念,只有对象的概念,

    更是有万物皆对象的说法。在基于类的面向对象方式中,对象(object)依靠类(class)来产生。

    而在基于原型的面向对象方式中,对象(object)则是依靠构造器(constructor) 利用 原型(prototype)

    构造出来的。而JavaScript语言正是如此,它是通过一种叫做原型(prototype)的方式来实现面向对象编程的。

    它和其他的面向对象类编程语言一样,只是它的实现方式不同而已,或者说他们采用了不同的面向对象设计哲学。

    那么下面就让我们来简单总结一下继承的几种方式:

    1. 扩展原型对象实现继承

    构造函数有一个 prototype 属性,指向的就是原型对象,通过给原型对象添加属性和方法,让构造函数的实例

    都可以访问到,从而实现继承

    function Animal(name,color,say){
      this.name = name;
      this.color = color;
      this.say = function(){
        console.log('喵喵喵');
      }
    }
    
    var cat = new Animal('cat','white');
    // 如果给Animal的prototype属性上添加个 cry 方法 ,那么实例对象 cat将也会有 cry方法 
    Animal.prototype.cry = function(){
      console.log('呜呜呜');
    }
    
    1. 替换原型对象实现继承 (常用类型)
    • 为什么会有该方式呢?
      其实上面扩展原型对象的方法已经很方便了,但是为什么我们还需要使用该方式呢?试想一下,
      当我们需要给构造函数的原型对象添加许多属性和方法的时候,岂不是要写很多冗余(重复)的代码。例如上面的例子
      看下面代码:

          // 我们要给 构造函数Animal 添加很多的方法 即:
          Animal.prototype.aaa = function(){};
          Animal.prototype.bbb = function(){};
          Animal.prototype.ccc = function(){};
          Animal.prototype.ddd = function(){};
          Animal.prototype.eee = function(){};
          ......  // 诸如这样的话,代码就显得不那么优雅,高效了吧。
      
    • 实现方式?
      重新给构造函数的prototype属性(原型对象)赋值,指向一个全新的对象,在这个对象中添加属性和方法,注:
      一定要添加一个constructor属性,并且指向构造函数本身 具体看代码:

          Animal.prototype = {
            // 一定要指明constructor属性,不然的话,会根据原型链查找一直到Object.prototype
            constructor : Animal;  
            saying : function(){},
            crying : function(){},
            doing : function(){}
           ......
          }
      
    1. 混入继承
    • 混入继承的使用场景:已知对象 o1, o2, 需要把 o1中的功能(属性方法)拷贝到 o2 中去

    • 实现方式 :用 for...in... 对 o 进行遍历(可以将混入继承的模式封装成函数),如下所示

        // target : 目标对象(接收数据的对象)
        // source : 接收对象 (数据从哪个对象中来)
        function mixin(target,source){
            for(var key in source){
              target[key] = source[key];
            }
            return target;
        }
      
    • 原理其实很简单,jQuery中的$.extend 方法利用的就是混入继承的原理

    1. 原型 + 混入继承
    • 本质上就是对混入继承的一次运用

    • 只不过目标对象为原型对象而已

            // 运用上面封装的混入继承的函数  将对象{a:10,b:20,c:function(){}}的功能考本到Animal原型对象上去
            mixin(Animal.prototype, {a:10,b:20,c:function(){}} );  
            // 还可以 这样操作 给Animal构造函数 的原型对象添加一个extend方法,在该方法中调用mixin函数,这样的话也可以实现
            Animal.prototype.extend = function(){
                 mixin(Animal.prototype, source);
            }
            // 不过两种方法没有本质之差,看大家易于接受哪个了
      
    1. 经典继承 —> 道格拉斯《JavaScript语言精粹》中提到的一种继承模型
    • 实现的功能:已知一个对象 o1 需要创建一个新的对象 o2 ,这个新的对象需要继承自对象 o1,代码如下:

          // 可以将经典继承 封装成一个函数
          function create(o){
            function F(){};    // 创建一个构造函数
            F.prototype = o;   // 将F 的原型指向 对象 o;
            return new F();    // 将 构造函数的实例 返回出去,这样的话 F的实例对象,就会继承自 o
          }
          var o2 = create(o1);  // 即:o2 继承于 o1 ;  o2.__proto__ = o1;
      
    • 使用场景 :要创建一个对象(不需要关心构造函数),新对象需要继承自另一个指定的对象

    • ES5中 :Object.create( ) 的实现原理就源自于经典继承

    1. 借用构造函数 实现继承
    • 先看一下代码 再解释:

        // 需要两个构造函数
        function Person(name,age,gender){  
         this.name = name;
         this.age = age;
         this.gender = gender;
        }
        // function Student(name,age,){
        //  this.name = name;
        // this.age = age;
        //  this.gender = gender;
        // }
        // 这样的话 name,age 属性都一样,就会产生重复的代码 我们可以巧妙的利用call 来简单的实现
        function Student(name,age){
         Person.call(this,name,age);     // 其实就是用 call的方法,call借用Person的功能
         this.gender = gender;
        }
      
      • 借用Person中的构造函数的功能,this表示构造函数的实例,即Student的实例对象可以继承name,age属性;

      • 借用构造函数实现继承,子构造函数借用父构造函数来完成,给子类Student的实例添加属性

      • 注意:由于要借用父类构造函数,所以父类构造函数的功能对子类对象要适用,例如下面情况就最好不用call

        // 需要两个构造函数
        function Person(name,age,gender){  
         this.name = name;
         this.age = age;
         this.gender = gender;
        }
        // function Student(name,age,){
        //  this.name = name;
        // this.age = age;
        // }
        function Student(name,age){
         Person.call(this,name,age);     
        }
        
    • 上述情况,就最好不要使用call来借用父类构造函数Person的功能了,因为,gender属性是Student子类构造函数
      并不需要的,这样的话,就会在Student中产生不必要的属性和方法,如果子类函数还要有gender方法的话,那么就会和之前的产生冲突,交叉污染

    • 其他情况:遇到需要实现功能,该对象上没有这个功能,可以适当地去寻找已经有功能的对象

  • 相关阅读:
    OpenJDK源码研究笔记(十二):JDBC中的元数据,数据库元数据(DatabaseMetaData),参数元数据(ParameterMetaData),结果集元数据(ResultSetMetaDa
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 241 为运算表达式设计优先级
    Java实现 LeetCode 241 为运算表达式设计优先级
    Java实现 LeetCode 241 为运算表达式设计优先级
  • 原文地址:https://www.cnblogs.com/guoqi77/p/Q_Inherit.html
Copyright © 2011-2022 走看看