前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >原型式继承和类式继承

原型式继承和类式继承

作者头像
Chor
发布2019-11-07 19:15:57
1.4K0
发布2019-11-07 19:15:57
举报
文章被收录于专栏:前端之旅前端之旅

Java和JavaScript都是面向对象的语言,但二者的继承方式截然不同。前者采用类式继承(classical inheritence),也是大多数面向对象语言的继承方式。而后者采用原型式继承(prototype ineritence),因此称JavaScript为基于对象更加合适。

1.JavaScript的继承

就JavaScript的继承来说,又可以分为es5的继承和es6的继承。参考阮一峰老师在《ES6标准入门》一书中所说的:

在ES6之前,class是保留字,ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

虽然在es6中引入了类的概念,但它其实只是简化了原来需要我们自己编写的原型链代码的语法糖,从而让js更趋向于传统的面向对象语言而已。要理解这个过程,首先要明白es6中的class做了什么。

1.1 class到底做了什么:
  • 首先看看class是什么东西
代码语言:javascript
复制
class Person{
    ......
}
typeof Person   //  function 

这里的class与java中的class不同,它并不是一个全新的数据类型,而是相当于原型继承中的构造函数。

  • 接着看看es5和es6在实现继承上的区别:

es5:

代码语言:javascript
复制
function Person(name){               //父类
    this.name = name;
}
Person.prototype.showName = function(){
    return this.name;
};
function SubPerson(name,job){         // 子类
    Person.call(this,name);  // 子类继承父类的属性 需要将this指向父类中的name
    this.job = job;       // job是子类的新增属性
}
SubPerson.prototype = new Person();  // 让子类继承父类的方法
var p1 = new SubPerson('zcl');      //实例化子类对象
console.log(p1.name);              // zcl(父类属性)
console.log(p1.showName());       // zcl(父类方法)

es6:

代码语言:javascript
复制
class Person{                      // 父类
    constructor(name){
        this.name = name;
    }
    showName(){
        return this.name;
    }
}
class SubPerson extends Person{          //子类
    constructor(name,job){
        super(name);      // 用super来调用父类的构造函数
        this.job = job;    // job是子类的新增属性
    }
    showJob(){
        return this.job;
    }
}
var p1 = new SubPerson('zcl','前端开发');    //实例化子类对象
console.log(p1.name);  // zcl(父类属性)
console.log(p1.showName());  // zcl(父类方法)
console.log(p1.job); // 前端开发(子类属性)

可以看到,es6中采用class后,大大简化了组合继承的步骤。

  • class做了什么:

1.定义父类时

代码语言:javascript
复制
class Person{
    constructor{ /*constructor*/ }
    method{ /*method*/ }
}
// 等价于
function Person{
    /*constructor*/
}
Person.prototype.method{
    /*method*/
}

2.子类继承父类时:

代码语言:javascript
复制
class SubPerson extends Person{
    onstructor{
         super(...)
         /*constructor*/       //子类新增属性
         }
    method{ /*method*/ }      //子类新增方法
}
var subperson1 = new SubPerson()

// 等价于

function SubPerson{
    Person.call(....)
    /*constructor*/          //子类新增属性
}
SubPerson.prototype = new Person()
SubPerson.prototype.method=function{
    /*method*/               //子类新增方法
}
var subperson1 = new SubPerson()

对于es6继承而言,访问实例化的子类对象的属性或者方法时,依然是沿着原型链进行追溯,并且子类实例创建后,class SubPerson中的this依然会指向该子类,可以看出,这与es5的原型继承的一模一样的。

1.2 关于this:

es5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.call(this));es6的继承机制完全不同,实质是先通过调用super方法(super指向父类的构造函数)创造父类的实例对象this,然后再用子类的构造函数修改this。如果子类没有定义constructor方法,这个方法会被默认添加。

2.Java的继承

首先了解java中创建对象的方式。java中,类一般包含field(变量),constructor(构造方法),method(其他方法)。

代码语言:javascript
复制
class Person{              // 创建父类
    private String name;   //  field
    public String getName(){    // method
        return this.name;
    };
    public Person(String name){  // constructor
        this.name = name;
    };
}

接着实现继承

代码语言:javascript
复制
class SubPerson extends Person{         // 创建子类
    private int age;          // field
    public String getAge(){       // method
        return this.age;
    };
    public SubPerson(String name,int age){   // constructor
        super(name);         // 通过super调用父类构造方法
        this.age = age;
    };
    public String getName(){  //重写父类方法,发生覆盖
        return "I am not "+super.getName()+" but I am "+this.name; 
    }
}

创建测试类

代码语言:javascript
复制
Public class Testclass{
    public static void main(String[] args){
        Person person1 = new Person("father")  
        //通过new一个构造方法创建父类实例
        SubPerson subperson1 = new SubPerson("son")  
        // 创建子类实例
        System.out.println(subperson1.getName());
        //->I am not father but I am son.
    }
}

3.JavaScript的原型式继承与Java的类式继承

由上面分析可见,es6中的类式继承其实还是原型式继承。那么它与java中真正的类式继承相比,有什么区别呢?

  • 类式继承的方法都会存在父对象之中,每一次实例,都会将funciton保存在内存中,这会带来性能上的问题。
  • 类式继承是不可变的。在运行时,无法修改或者添加新的方法,这种方式是一种固步自封的死方法;相反,原型继承是可以通过改变原型链进而对子类进行修改的。
  • 类式继承可以实现多重继承(Java是通过接口来实现);原型式继承一般来说是不支持多继承的(因为原型链),但可以通过Mixin变相实现多继承。

4.补充:子类如何调用父类被覆盖的同名方法

首先这是一个比较奇怪的需求,因为既然子类重写了父类方法,就说明父类方法无法实现我们的要求,反过来,假设父类方法可以实现要求,则没必要重写该方法。但是让我们设想一下,假定现在一定要通过子类调用父类被覆盖的那个方法,应该怎么做呢?

通过上面的例子可以看到,在java中,我们只能在子类的构造方法中通过super关键字调用父类方法,而无法直接用子类的实例调用那个方法,像“子类实例.super.父类方法”,这是无效的;但是在js中,我们是可以做到的。基本思路就是:将父类实例以属性的方式进行保存,且该属性是子类构造函数的原型对象的属性。

这其实和原型链有关。我们设想有父类A、子类B以及同名方法say,并且设定子类B的原型对象的superClass属性指向父类实例b。那么,子类实例a直接调用say方法,必然是调用重写之后的方法;当它想要调用被覆盖的方法时,我们只需要用a.superClass.say()即可—–对于实例a,我们知道它本身并不具备superClass属性,因此它将沿着自己的原型对象也即子类B的原型对象进行查找,刚好B的原型对象有一个指向b的superClass属性,所以我们拿来用,而b有被覆盖的say方法,所以这里顺利完成了被覆盖方法的调用。

附上CSDN的参考链接

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.JavaScript的继承
    • 1.1 class到底做了什么:
      • 1.2 关于this:
      • 2.Java的继承
      • 3.JavaScript的原型式继承与Java的类式继承
      • 4.补充:子类如何调用父类被覆盖的同名方法
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档