JS的5种继承方式

当前问题总结

image.png

源代码片段如上,今天在看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覆盖。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容

  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,107评论 0 6
  • 面向对象编程是用抽象方式创建基于现实世界模型的一种编程模式,主要包括模块化、多态、和封装几种技术。对 JavaSc...
    吴佳浩阅读 511评论 0 4
  • avaScript对每个创建的对象都会设置一个原型,指向它的原型对象。 当我们用obj.xxx访问一个对象的属性时...
    源大侠阅读 253评论 0 1
  • Effective Objective C 2.0:编写高质量iOS与OS X代码的52个有效方法 第 37 条:...
    0db99e947190阅读 130评论 0 0
  • 今个儿七夕。 刚巧是小果人生中第一次迎来新阶段,很坚强的小盆友,不哭不闹上完了一天的幼儿园。晚上接的时候很淡定,回...
    路旁的过小过阅读 145评论 0 0