Java和JavaScript都是面向对象的语言,但二者的继承方式截然不同。前者采用类式继承(classical inheritence),也是大多数面向对象语言的继承方式。而后者采用原型式继承(prototype ineritence),因此称JavaScript为基于对象更加合适。
就JavaScript的继承来说,又可以分为es5的继承和es6的继承。参考阮一峰老师在《ES6标准入门》一书中所说的:
在ES6之前,class是保留字,ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
虽然在es6中引入了类的概念,但它其实只是简化了原来需要我们自己编写的原型链代码的语法糖,从而让js更趋向于传统的面向对象语言而已。要理解这个过程,首先要明白es6中的class做了什么。
class Person{
......
}
typeof Person // function
这里的class与java中的class不同,它并不是一个全新的数据类型,而是相当于原型继承中的构造函数。
es5:
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:
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后,大大简化了组合继承的步骤。
1.定义父类时
class Person{
constructor{ /*constructor*/ }
method{ /*method*/ }
}
// 等价于
function Person{
/*constructor*/
}
Person.prototype.method{
/*method*/
}
2.子类继承父类时:
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的原型继承的一模一样的。
es5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.call(this));es6的继承机制完全不同,实质是先通过调用super方法(super指向父类的构造函数)创造父类的实例对象this,然后再用子类的构造函数修改this。如果子类没有定义constructor方法,这个方法会被默认添加。
首先了解java中创建对象的方式。java中,类一般包含field(变量),constructor(构造方法),method(其他方法)。
class Person{ // 创建父类
private String name; // field
public String getName(){ // method
return this.name;
};
public Person(String name){ // constructor
this.name = name;
};
}
接着实现继承
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;
}
}
创建测试类
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.
}
}
由上面分析可见,es6中的类式继承其实还是原型式继承。那么它与java中真正的类式继承相比,有什么区别呢?
首先这是一个比较奇怪的需求,因为既然子类重写了父类方法,就说明父类方法无法实现我们的要求,反过来,假设父类方法可以实现要求,则没必要重写该方法。但是让我们设想一下,假定现在一定要通过子类调用父类被覆盖的那个方法,应该怎么做呢?
通过上面的例子可以看到,在java中,我们只能在子类的构造方法中通过super关键字调用父类方法,而无法直接用子类的实例调用那个方法,像“子类实例.super.父类方法”,这是无效的;但是在js中,我们是可以做到的。基本思路就是:将父类实例以属性的方式进行保存,且该属性是子类构造函数的原型对象的属性。
这其实和原型链有关。我们设想有父类A、子类B以及同名方法say,并且设定子类B的原型对象的superClass属性指向父类实例b。那么,子类实例a直接调用say方法,必然是调用重写之后的方法;当它想要调用被覆盖的方法时,我们只需要用a.superClass.say()
即可—–对于实例a,我们知道它本身并不具备superClass属性,因此它将沿着自己的原型对象也即子类B的原型对象进行查找,刚好B的原型对象有一个指向b的superClass属性,所以我们拿来用,而b有被覆盖的say方法,所以这里顺利完成了被覆盖方法的调用。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有