JavaScript深入系列之继承的方法

作者 : 开心源码 本文共5531个字,预计阅读时间需要14分钟 发布时间: 2022-05-13 共261人阅读

前言

有半年没升级文章,上半年一直处于焦虑的状态,不怎样想动手提笔,直到现在,才意识到,一直焦虑下去也不是办法的,不如沉淀自己,提升自己的,即便个人有些特殊情况的,但我相信金子总会闪闪发光的,还是不会闪闪发光的话,但至少我试过~

image

今天是中秋国庆长假最后一天,抓住尾巴写完一篇文章,继续写上一篇的续集~

此文需要准备好电脑,大脑,一杯咖啡

1.原型链继承

上一篇文章详情到过原型链的概念,其实这是一种继承的方法之一,由于在ECMAScript是没有类的概念,然而有些时候自己具备独立的属性或者方法,但有些地方跟另一个对象的属性或者方法很像的,比方说我们之前新建过一个动物Animal类,而后这个类包含名字,身高,爱吃什么,有多重呢等等?代码如下:

function Animal(name,age,height){    this.name=name;    this.age=age;    this.height=height;}Animal.prototype.eatFood=function(food){    console.log(`${this.name}喜欢吃${food}`);}Animal.prototype.getWeight=function(weight){    console.log(`${this.name}体重有${weight}kg`);}let buouCat = new Animal('布偶猫',3,10);buouCat.eatFood('?'); //布偶猫喜欢吃?buouCat.getWeight(30); //布偶猫体重有30kg

而后有一天新的需求来了,需要对鸟类进行详细的记录呢,是不是会飞的,还要有名字,多大,多高,喜欢吃什么,有多重呢?想看看这个鸟类也是动物类之一的,需要记录的东西,Animal类就有的,所以只需继承就好了,而怎样实现继承的呢?还是利用原型让一个对象继承另一个对象的属性和方法,看?code:

//新添加的代码function Bird(isFly){    this.isFly = isFly;}Bird.prototype = new Animal();Bird.prototype.isOrFly = function(){    console.log(`${this.name}是${this.isFly ? '会飞' : '不会飞'}的鸟类`);};let b1 = new Bird(true);b1.name = "燕子";b1.isOrFly(); // 燕子是会飞的鸟类b1.eatFood('虫子'); //燕子喜欢吃虫子

利用Bird的原型来实例化Animal构造函数的,而后Bird的原型指针向Animal,这就扩展了原型搜索的,首先会在Bird搜索该属性,找不到的就沿着Bird的原型往上搜索的,找到的就会返回,找不到的继续往上搜索,直到原型链的末端才停下来呢,这就是原型链的继承方法。当然假如Bird类重写喜欢吃什么样的虫子,就会覆盖Animal类的eatFood方法,看?code:

Bird.prototype.eatFood(food1,food2){    console.log(`${this.name}喜欢吃${food1},但它一般吃${food2}`);}b1.eatFood('大米','虫子'); //燕子喜欢吃大米,但它一般吃虫子。

原型链看起来很强大的,但它还是有弱点的,上一篇文章中的“原型”详情到过的,就是在引用类型方面上还是会影响到所有的实例对象,原型链也是如此的,看?code:

//如果在Animal构造函数上新添加一个引用类型的属性function Animal(name){    this.name = name    this.info={        isSleep: true,        isEat: true,    }}Animal.prototype.eatFood=function(food){    console.log(`${this.name}喜欢吃${food}`);}Animal.prototype.getWeight=function(weight){    console.log(`${this.name}体重有${weight}kg`);}function Bird(){}Bird.prototype = new Animal();let b1 = new Bird();let b2 = new Bird();//而后我要修改b1的info引用类型b1.info.isSleep=false;//而后再看看b2的info有没有变化呢?console.log(b2.info.isSleep); //false

哈?不应该啊,我只是想改下b1这一个的属性而已呢,结果却影响到b2的,那是由于Animal的所有属性是在Bird的prototype上呢,而后Bird实例化b1和b2对象,它们就有同一个prototype属性,要改b1的info,也就相当于改变prototype上的info,而后读取b2的info时,就是从该prototype的info属性(此时这个已经被改变了),所以才会返回false;所以这就是原型链的一个弱点,所以出现借用构造函数

2.借用构造函数

说完整点,就是一个子类型构造函数里借父类型的构造函数来调用,因而可以通过call和apply来执行的,这样Bird就拥有Animal的所有属性,这就是借用构造函数的过程,优点是实例化多个对象时,每个实例对象都是独立的,互不影响,看?code:

function Animal(name){    this.name = name    this.info={        isSleep: true,        isEat: true,    }}function Bird(){    Animal.call(this);}let b1 = new Bird();let b2 = new bird();b1.info.isSleep = false;b2.info.isSleep = true;//没被影响到的

很遗憾的,仅靠借用构造函数的话,就是没办法复用父类型的原型对象,看?Code:

function Animal(name){    this.name = name    this.info={        isSleep: true,        isEat: true,    }}Animal.prototype.sayName = function(){    console.log(`这个动物的名字是${this.name}`);}function Bird(name){    Animal.call(this,name);}let b1 = new Bird('麻雀'); b1.sayName(); //报错:VM3962:1 Uncaught TypeError: b1.sayName is not a function

找不到b1.sayName(), 由于call或者者apply只能执行构造函数的,并不包括构造函数的原型对象,所以找不到的,才会报错的,所以这就是缺点,这就出现组合继承的概念,不得不感叹es原来有这么多故事呢~

3.组合继承

尽管原型链和借用构造函数都有缺点的,但我们不能由于白板上的一个小黑点就否定所有的,它们还是有自己的优点,原型链优点就是能继承原型属性和方法,而借用构造函数优点就是实例对象能独立继承构造函数的属性,所以把这两个的优点组合在一起,就称为组合继承的,怎样实现,还是用上面的代码来优化下,看?code:

function Animal(name){    this.name = name    this.info={        isSleep: true,        isEat: true,    }}Animal.prototype.sayName = function(){    console.log(`这个动物的名字是${this.name}`);}function Bird(name){    //借用构造函数继承属性    Animal.call(this,name);}//新添加代码Bird.prototype = new Animal();//原型链继承let b1 = new Bird('麻雀'); b1.sayName(); //这个动物的名字是麻雀let b2 = new Bird('猫头鹰');b2.info.isSleep = false;b1.info.isSleep //true;

改变info里的isSleep,不影响另一个实例对象的info(借用构造函数的功劳),而后又能打印sayName方法(原型链的功劳)~所以这个成为JavaScript最常用的继承方法,所以说这是最完美的么?并不是呢,它也有不足的,稍后再说这个~

4.原型式继承

和上一篇文章中的原型模式相似,利用原型把一个已知对象赋值给未知对象,看?代码:

function object(o){    function F(){}    F.prototype = o    return new F();}let animal = {    name:'?',    info:{        isSleep: true,        isEat: true    }}let cat = object(animal);cat.name = '?';cat.name; //?cat.info.isCute = true;animal.info; //{isSleep: true, isEat: true, isCute: true}

要说优点,假如就仅仅要求对象一样的,可以考虑用这个的,但引用类型还是一个缺点的,和原型链缺点一样的,所以谨慎用~

5.寄生式继承

这个是在原型式继承上改进的,创立一个封装继承过程的函数,而后返回该对象,用法比较像工厂模式,看?code:

function object(o){    function F(){}    F.prototype = o    return new F();}function create(obj){    let clone = object(obj);    clone.sayName = function(){        cobsole.log(this.name);    }    return clone;}let animal = {    name:'?',    info:{        isSleep: true,        isEat: true    }}let a1 = create(animal);a1.sayName(); // '?'

缺点就是不能实现函数的复用~但它的优点还是可以值得借鉴,何出此言呢?继续看?就知道了~

6.寄生组合式继承

嗯,寄生式+组合式来继承的,组合式继承上面讲到过它也有不足之处,不足在哪儿呢?我们先看看code:

function Animal(name){    this.name = name    this.info={        isSleep: true,        isEat: true,    }}Animal.prototype.sayName = function(){    console.log(`这个动物的名字是${this.name}`);}function Bird(name){    Animal.call(this,name);//第二次调用Animal构造函数}Bird.prototype = new Animal();//第一次调用Animal构造函数let b1 = new Bird('麻雀'); b1.sayName(); //这个动物的名字是麻雀let b2 = new Bird('猫头鹰');b2.info.isSleep = false;b1.info.isSleep //true;

其中Bird类两次调用Animal构造函数,第一次是在Bird原型上调用Animal构造函数Bird.prototype=new Animal(),Bird.prototype就有了name和info,第二次是调用Bird构造函数时,又会调用Animal构造函数Animal.call(this,name),而后b1有了name和info,屏蔽Bird.prototype上的name和info,看下面的截图:

image

我们原本目的是继承父类的原型对象,并不包括构造函数的所有属性,所以我们需要的无非就是父类的原型的一个副本而已呢,所以这就需要用到寄生式继承的,可以把父类的原型对象看作一个对象,而后赋值给子类的原型

function object(o){    function F(){}    F.prototype = o    return new F();}function clonePrototype(child,parent){    let pro = object(parent.prototype); //新建一个对象    pro.constructor = child; //别忘把construcotr指向子类型,这样instanceof才能正常使用    child.prototype = pro; //赋值给子类型的原型}function Animal(name){    this.name = name    this.info={        isSleep: true,        isEat: true,    }}Animal.prototype.sayName = function(){    console.log(`这个动物的名字是${this.name}`);}function Bird(name){    Animal.call(this,name);}clonePrototype(Bird,Animal);Bird.prototype = new Animal();//第一次调用Animal构造函数let b1 = new Bird('麻雀'); b1.sayName(); //这个动物的名字是麻雀

而后我们来看看用寄生组合式继承的打印结果:

image

可以发现,Bird的原型上没有name和info,这即可以避免重复的,与此同时还能保持原型链的特征,所以这个是最佳的继承方法~

好了,咖啡喝完了,就到此为止~如有错误之处,请多多指出的,笔芯~

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » JavaScript深入系列之继承的方法

发表回复