当前问题总结
源代码片段如上,今天在看AntV/G2源码的时候,发现其中Chart类中继承了View类,照理在子类Chart类中应该有一个构造函数,并且在构造函数中调用super方法来实现父类属性和方法的继承,如以下代码:
//person父类
class person {
constructor(name){
this.name=name;
}
eat(food){
console.log(this.name+" is eating "+food);
}
}
//Student子类继承person父类
class Student extends person{
constructor(name){
super(name);//调用父类的构造函数
}
}
var martin=new Student("martin");
martin.eat("apple");//martin is eating apple
但是Chart类中并没有找到构造函数来实现继承View类,那它为什么又能实现继承呢?
通过调查资料发现,construct有个默认定义的规则,规则如下:
- 对于基类(就是最原始没有继承其他类的类,这里指person类),默认构造函数为:
注意:基类不等于父类
constructor() {}
- 对于派生类(就是继承其他类的类,这里指Student类),默认构造函数为:
注意:派生类也不等于子类
constructor(...args) {
super(...args);
}
正因为有这个默认规则,所以派生类Chart可以实现继承View的属性和方法。
JS继承方式
这个问题让我感觉自己对继承方面知识的掌握还有所欠缺,所以索性整理了一下实现继承的6种方式。
首先我们先建立一个“人”对象的构造函数。
function person(){
this.kind="person";
}
person.prototype.eat=function(food){
console.log(this.name+" is eating "+food);
}
这个“人”对象的构造函数包括一个kind属性和一个eat方法。
还有一个表示人里面“学生”对象的构造函数
function student(name) {
this.name=name;
}
那如何让“学生”对象继承“人”对象的kind属性和eat方法呢?
构造函数继承
这一种方法最为简单,直接利用call或者apply方法将父类构造函数的this绑定为子类构造函数的this就可以
function student(name) {
person.apply(this,arguments)
this.name=name;
}
var martin=new student("martin");
console.log(martin.kind); //person
martin.eat("apple"); //报错
缺点:我们可以看出,这种方式的继承只能继承父类构造函数中的属性和方法,对于原型对象无法继承。
原型实例继承
这是比较常用的一种实现继承的方式。
student.prototype=new person();
student.prototype.construct=student;
var martin=new student("martin");
martin.eat("apple"); //martin is eating apple
console.log(martin.kind);//person
student.prototype=new person();
代码的第一行,我们将student的prototype对象指向person的一个实例。这句话相当于完全删除了student.prototype对象原本的内容,然后赋予了它一个新的值。
student.prototype.construct=student;
代码的第二行,因为任何一个构造函数都有一个prototype对象,这个prototype对象都有一个constructor属性指向自身的构造函数。但是因为第一行对prototype对象重新进行了赋值,所以prototype对象的constructor属性也发生了改变,变成指向person,所以必须手动将student.prototype.constructor指回student。
如果没有这一行代码:
console.log(student.prototype.constructor==person); //true
console.log(martin.constructor==person); //true
这里比较好理解,因为martin是student的实例化对象,它的constructor属性默认继承自person.prototype,而person.prototype的constructor属性这里继承自person.prototype,最后找到person.prototype.constructor指向person。显然如果没有这句话,将会导致继承链的错乱。
注意:以后当我们编程时,如果对prototype对象进行了重新赋值
fun.prototype={object};
必须手动要将prototype的constructor属性指回原来的构造函数;
fun.prototype.constructor=fun;
原型直接继承
这一种方法是通过直接将子类构造函数的原型对象直接赋值为父类构造函数的原型对象,从而达到继承的目的。
student.prototype=person.prototype;
student.prototype.constructor=student; //注意这一行产生的变化
var martin=new student("martin");
martin.eat("apple"); //martin is eating apple
console.log(martin.kind); //undefined
缺点:我们可以看出,这种方式无法继承父类构造函数中的属性与方法,但是可以继承父类构造函数的原型对象。
另外,因为子类原型student.prototype和父类原型person.prototype指向的是同一个地址,所以student.prototype任何的改变都会引起person.prototype的改变。
student.prototype.constructor=student;
这一行我们上面说过,因为student.prototype原型对象被重新赋值了,所以需要将constuctor属性手动指回student,这是没有问题的,但是实际上这句话把person.prototype的constructor属性也一起改成了student。
console.log(person.prototype.constructor); //student
ES6中Class继承
class继承是ES6中新出的语法糖,通过class我们可以很简洁的实现继承,实现代码如下:
class person {
constructor(){
this.kind="person"
}
eat(food){
console.log(this.name+" "+food);
}
}
class student extends person{
constructor(name){
super();
this.name=name;
}
}
var martin=new student("martin");
console.log(martin.kind); //person
martin.eat("apple"); //martin apple
以上都是针对构造函数继承的方法,那如果是两个对象之间又如何实现继承呢,下面就介绍一种对象间的继承方法。
深拷贝继承
这种方式主要通过对象间的深拷贝来实现继承。
function deepCopy(parent,child) {
var child=child||{};
for(var i in parent){
if(typeof parent[i]==="object"){
child[i]=parent[i].constructor==="Array"?[]:{};
deepCopy(parent[i],child[i]); //递归
}else{
child[i]=parent[i];
}
}
return child
}
var parent={
name:"martin",
say(){
console.log("say"+this.name);
}
};
var child={
name:"lucy",
kind:"person",
eat(){
console.log("eating"+this.name);
}
};
deepCopy(parent,child);
console.log(child); //{ kind: 'person',eat: [Function: eat],name: 'martin',say: [Function: say] }
我们可以发现在child对象深拷贝parent对象后,child对象拥有了parent所有的属性和方法。
console.log(child.name); //martin
在深拷贝后,如果child有和parent相同的属性或方法,child的属性或方法会被parent的属性或方法所覆盖。如果不想他们之间互相覆盖,可以将else中加一个验证,验证child中是否有parent的属性,如果有则保留child原本属性。
else{
if(!(i in child)){ //i in child表示属性i是否在child对象中,如果有则返回true
child[i]=parent[i];
}
}
console.log(child.name); //lucy
改变之后我们发现child中与parent中相同的属性name保留了child原来的属性值,并没有被parent覆盖。