zoukankan      html  css  js  c++  java
  • Javascript实现对象的继承

    在Java和C#中,你可以简单的理解class是一个模子,对象就是被这个模子压出来的一批一批月饼。压个啥样,就得是个啥样,不能随便动,动一动就坏了。
    而在Javascript中,没有模子,月饼被换成了面团,你可以捏成自己想要的样子。对象属性,方法不合适,可以修改。没有的方法,可以自己创建,发挥的自由度很大。

    继承是指一个对象直接使用另一对象的属性和方法。

    许多面向对象语言都支持两种继承方式:接口继承实现继承
    接口继承:只继承方法签名;
    实现继承:继承实际的方法。
    Javascript是弱类型语言,大小写敏感。它不是原生支持继承,而是通过prototype去模拟的,所以是基于对象,不是面向对象。在ECMAScript中,无法实现接口继承,只支持实现继承,而实现继承主要是依靠原型链。每个函数都有call,apply方法,都有length,arguments,caller等属性。为什么每个函数都有?这肯定是"继承"的。函数由Function函数创建,因此继承Function.prototype中的方法。
    ES5继承是先创建子类的实例对象this,再向this对象中添加父类的方法;
    ES6继承是先创造父类的实例对象this,再用子类的构造函数修改this。

    ECMAScript继承主要分两大类:引用对象继承实例对象继承
    一、引用对象继承
    子引用类型继承父引用类型,然后通过子引用类型生成的实例对象,具有父引用类型的特性。

    // 定义一个父类,公用
    function Parent(name,age){
      // 属性
      this.name = name || 'Parent';
      this.age = age || 18;
      // 实例方法
      this.getAge = function(){
        console.log('我的年龄是:'+ this.age);
      }
    }
    // 原型方法
    Parent.prototype.getName = function() {
      console.log('我的名字是:'+ this.name);
    };

     1、原型链继承

    function Child(){ 
    }
    Child.prototype = new Parent();//借助JavaScript中的委托机制,将父类的实例作为子类的原型。
    Child.prototype.constructor = Child;
    Child.prototype.name = 'child1';
    
    //test
    var child1 = new Child();
    console.log(child1.name);
    console.log(child1.getAge());
    console.log(child1.getName());
    console.log(child1 instanceof Parent); //true 
    console.log(child1 instanceof Child); //true

    【特点】
    A、父类新增原型方法/原型属性,子类都能访问到;
    B、简单,易于实现;
    C、非常纯粹的继承关系,实例是子类的实例,也是父类的实例。
    【缺点】
    A、来自原型对象的引用属性是所有实例共享的;
    B、创建子类实例时,无法向父类构造函数传参;
    C、要想为子类新增属性和方法,必须要在new Parent()后执行;
    D、无法实现多继承。

    2、构造继承(伪造对象,经典继承,对象冒充继承)

    function Child(name){
      Parent.call(this);//在子类内部调用父类,通过call,apply改变对象的this指向来继承。
      //Parent.apply(this, arguments);
      this.name = name || 'Camille';
    }
    
    //test
    var child1 = new Child();
    console.log(child1.name);
    console.log(child1.getAge());
    console.log(child1 instanceof Parent); // false
    console.log(child1 instanceof Child); // true

    【特点】
    A、避免了引用类型的属性被所有实例共享;
    B、可以在Child中向Parent传参;
    C、可以实现多继承,call多个父类对象。
    【缺点】
    A、实例并不是父类的实例,只是子类的实例;
    B、只能继承父类的实例属性和方法,不能继承原型属性/方法;
    C、无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。

    3、组合继承,次优(类式继承)

    function Child(name,age){
      Parent.call(this);//通过调用父类构造函数,继承父类的实例属性并保留传参的优点。
      this.name = name || 'Camille';
      this.age = age;
    }
    Child.prototype = new Parent();//通过将父类实例作为子类原型,实现函数复用及对原型属性和方法的继承。
    
    Child.prototype.constructor = Child;
    
    //test
    var child1 = new Child('HouYi',20);
    console.log(child1.name);
    console.log(child1.getAge());
    console.log(child1 instanceof Parent); // true
    console.log(child1 instanceof Child); // true

    【特点】
    A、可以继承实例属性/方法,也可以继承原型属性/方法;
    B、既是子类的实例,也是父类的实例;
    C、可传参;
    D、函数可复用。
    【缺点】
    A、调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)。

    4、寄生继承

    function createObj(o){
      var F = function(){};
      F.prototype = o;
      return new F();
    }
    function getObj(obj){
      var cobj = createObj(obj);//创建新对象,通过寄生方式,砍掉父类的实例属性
      cobj.setSex = function(){//增强对象
        console.log('新对象的性别设置函数');
      };
      return cobj;//指定对象
    }
    
    //test
    var psl = new Parent();
    var child1 = getObj(psl);
    console.log(child1.name);
    console.log(child1.setSex());
    console.log(child1 instanceof Parent); // true

    【特点】
    A、给原生继承穿了个马甲,更像继承了;
    B、createObj函数不是必须的,新对象是如何创建的并不重要,用createObj生的,new出来的,字面量现做的,都可以。
    【缺点】
    A、无法实现函数复用(没用到原型,当然不行)。

    5、寄生组合继承,最优

    function createObj(o){
      var F = function(){};
      F.prototype = o;
      return new F();
    }
    function Child(name,age){
      Parent.call(this); 
      this.name = name || 'Camille';
      this.age = age;
    }
    var nobj = createObj(Parent.prototype);//通过寄生方式,砍掉父类的实例属性,创建对象
      nobj.constructor = Child;//增强对象
      Child.prototype = nobj;//指定对象
    
    //test
    var child1 = new Child('HouYi',27);
    console.log(child1.name);
    console.log(child1.getAge());
    console.log(child1 instanceof Parent); // true
    console.log(child1 instanceof Child); // true

    【特点】
    A、完美,在调用两次父类的构造的时候,不会初始化两次实例方法/属性;
    B、对实例属性和原型属性分别进行了继承。
    【缺点】
    A、实现复杂。

    二、实例对象继承
    继承得到的对象都具有父实例对象的所有属性和方法,其实就是指对象的复制克隆
    1、原型继承

    // 定义一个父类,公用
    function Parent(name,age){
      // 属性
      this.name = name || 'Parent';
      this.age = age || 18;
      // 实例方法
      this.getAge = function(){
        console.log('我的年龄是:'+ this.age);
      }
    }
    // 原型方法
    Parent.prototype.getName = function() {
      console.log('我的名字是:'+ this.name);
    };
    /**
     * 创建一个继承父类原型的实例对象,这也是ES5中Object.create()的简单实现。
     * 在object()函数内部, 先创建一个临时性的构造函数;
     * 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。
     */
    function createObj(o){
      var F = function(){};
      F.prototype = o;
      return new F();
    }
    
    //test
    var psl = new Parent();
    var child1 = createObj(psl);//这是重点
    child1.name = 'Camille';
    console.log(child1.name);
    console.log(child1.getAge());
    console.log(child1 instanceof Parent); // true

    ECMAScript5通过新增Object.create()方法规范化了原型式继承,这个方法接收两个参数:一个用作新对象原型的对象和一个作为新对象定义额外属性的对象。

    var child1 = Object.create(psl);
    console.log(child1.name);
    console.log(child1.getAge());
    console.log(child1 instanceof Parent); // true

    【特点】
    A、复制父实例对象;
    【缺点】
    A、原型引用属性会被所有实例共享;
    B、无法实现代码复用(新对象是现取的,属性是现添的,都没用函数封装,怎么复用)。

    2、拷贝继承

    3、借用
    有时可能恰好仅需要父对象其中的一两个方法,而不需要父对象的其他方法。这种情况下,可以使用call()和apply()来实现继承,区别就是传参的不同。
    A、比如,借用Array中的slice方法

    Array.prototype.slice.call();
    [].prototype.slice.call();

    B、比如,借用对象的方法

    //如果getAge的this指向childObj
    var parentObj = {
      name:'Parent',
      getAge:function(age){
        console.log('我的年龄是:'+ age);
      }
    }
    var childObj = {
      name:'camille'
    }
    parentObj.getAge.call(childObj, '18'); //我的年龄是:18
    //如果getAge的this指向全局对象
    var parentObj = {
      name:'Parent',
      getAge:function(age){
        console.log('我的年龄是:'+ age);
      }
    }
    var pgetage = parentObj.getAge;
    pgetage("27");//我的年龄是:27
    
    var childObj = {
      name:'Camille'
    }
    function bind(o,m){
      return function(){
        return m.apply(o,arguments);
      };
    }
    var cgetage = bind(childObj,parentObj.getAge);
    cgetage("30");//我的年龄是:30

    4、绑定
    ES5将bind()添加到Function.prototype,使得bind()像call()apply()一样易用。

    // bind的内部实现
    if(typeof Function.prototype.bind === 'undefined'){
      Function.prototype.bind = function(thisArg){
        var _this = this, 
        slice = Array.prototype.slice, 
        args = slice.call(arguments, 1);
        return function(){
          return _this.apply(thisArg, args.concat(slice.call(arguments)));
        }
      }
    }
    var parentObj = {
      name:'Parent',
      getAge:function(age){
        console.log('我的年龄是:'+ age);
      }
    }
    var childObj = {
      name:'Camille'
    }
    var cgetage = parentObj.getAge.bind(childObj, '32'); 
    cgetage();//我的年龄是:32

    三、ES6中的继承

    使用class,extends,super关键字来实现类继承。

    //父类 Parentc
    class Parentc {
      // 父类的构造方法
      constructor(name, age) {
        this.name = name;
        this.age = age;
        // 共享变量
        this.LEGS_NUM = 2;
      }
      // 父类的getName方法
      getName() {
        console.log(`我的名字是:${this.name}`);
      }
    
      // 父类的getAge方法
      getAge() {
        console.log('我的年龄是:' + this.age);
      }
    
    }
    //子类 Childc
    class Childc extends Parentc {
      constructor(name, age, city) {
        // 调用父类的构造方法
        super(name, age);
        this.city = city;
      }
    
      // 覆盖父类的getName方法
      getName() {
        console.log(`我的名字是:${this.name},来自${this.city}`);
      }
    }
    
    // 实例化一个Childc的实例
    let aperson = new Childc('Camille', 18, '西安');
    aperson.getName(); // 我的名字是:Camille,来自西安
    aperson.getAge(); // 我的年龄是18
    console.log(aperson.LEGS_NUM); // 2
    console.log(aperson instanceof Childc); //true
    console.log(aperson instanceof Parentc); //true
  • 相关阅读:
    OGRE 3D 1.7 Beginner‘s Guide中文版 第一章
    一个人的成功取决于晚上的8点至10点--经典语录必读
    学历代表过去、能力代表现在、学习力代表未来
    理财达人五步走
    Ogre场景、节点、摄像机通过自动、鼠标、键盘控制移动
    QT按钮背景颜色设置及文字显示位置设置
    Qt一个工程调用另一个工程的类成员变量
    C++搜索字符串中的汉字
    Q窗口操作函数(窗口最大化,全屏,隐藏最大化最小化按钮)
    PAT(Advance Level)Practice1001
  • 原文地址:https://www.cnblogs.com/camille666/p/js_inherit.html
Copyright © 2011-2022 走看看