javascript学习笔记(19)--原型继承

引入

在传统的基于Class的语言如Java、C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass
由于这类语言严格区分类和实例,继承实际上是类型的扩展
但是,JavaScript由于采用原型继承,我们无法直接扩展一个 Class,因为根本不存在Class这种类型, 那么该怎么办呢?

我们先回顾Student构造函数

function Student(stdt) { 
this.name = stdt.name; 
Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); }
}

之后我们创建实例最好就传入对象,这样我们自己也不用考虑顺序,也好操作

现在,我们要基于Student扩展出PrimaryStudent,可以先定义出PrimaryStudent

 function PrimaryStudent(stdt) { 
 Student.call(this, stdt);
  this.grade = stdt.grade || 1; }

call的用法直接也讲过啦,这里在简单提一下,
f.call(this,x,y,z)
this是我们要绑定的对象,x,y,z是我们f的参数,这里我们参数只有一个对象,于是就只传入stdt

但是,调用了Student构造函数不等于继承了Student
在这里插入图片描述额这个是函数的原型链
PrimaryStudent创建的对象的原型链
在这里插入图片描述
new PrimaryStudent() ----> PrimaryStudent.prototype ----> Object.prototype ----> null
不过之后我们还是主要关注实例的原型链,因为最终我们需要的其实是一个个实例
在js种,创建实例其实是通过函数生成的,一个个函数,我们既可以当他们是一个个具体的函数实例,比如说Math.abs,它可以计算,这个时候就是一个具体的实例了,也可以当成是一个类,比如说这里的Student,我们不会去调用它,但我们会利用它扩展生成一类实例

因为我们是通过Student扩展出的PrimaryStudent,所以我们希望原型链为
xiaoming ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null
这样,原型链对了,继承关系就对了,新的基于Primary Student创建的对象不但能调用PrimaryStudent.prototype定义的方法,也可以调用Student.prototype定义的方法 ,这才是我们想要的继承
这样的话其实也可以解决我们的代码负担,免得还要把方法再写一遍

解决方法

第一种
因为我们PrimaryStudent.prototype指向的是Object.prototype我们修改一下
在这里插入图片描述在这里插入图片描述我们只需要把PrimaryStudent.prototype的上一级改成Student.prototype就好了呀
在这里插入图片描述
而且也不影响函数的原型链

第二种(也是老的方法)
我们可以借助一个中间对象来实现正确的原型链,这个中间对象的原型要指向Student.prototype,为了实现这一点,中间对象可以用一个空函数F来实现
首先看看如果一个函数修改了prototype会有什么变化
在这里插入图片描述在这里插入图片描述(最下面灰色的是函数的原型链,跟我们这里没关系,而且我看了,确实也不影响)
修改了prototype之后的F果然是大变活人呀,真的一摸一样
在这里插入图片描述刚才第一种我是这样修改的,没啥问题

function F(){};
F.prototype = Student.prototype;

但如果定义在了函数内部,记着要调用一下自己哟
在这里插入图片描述好啦,下面开始我们继承啦

 function PrimaryStudent(stdt) { 
 Student.call(this, stdt); 
 this.grade = stdt.grade || 1;
  }
function F() { };//因为怕自己忘记调用了,还是我们手动修改
F.prototype = Student.prototype;
 PrimaryStudent.prototype = new F(); 

在这里插入图片描述这样继承不知道能不能理解
之前我们的new F()是用来创建实例,这里我们把new F()创建的实例赋值给了PrimaryStudent,其实也不要把new F()想狭隘了,new F()就是给你创造一个空的对象,然后里面给你加个继承关系,刚好适合我们这里的prototype
(这是原来的PrimartStudent)
这里获得好了继承关系后,但是我们没有构造函数了,因为new的作用机理,给的是空对象和继承关系,把你原来的prototype里的内容清空了,所以我们自己需要把构造函数补上

PrimaryStudent.prototype.constructor = PrimaryStudent;

在这里插入图片描述跟修改之前看起来差不多,但其实继承关系变啦
在这里插入图片描述第三种方法
只是把第二种方法变成个函数
如果把继承这个动作用一个inherits()函数封装起来,还可以隐藏F的定义,并简化代码:

 function inherits(Child, Parent) { 
 var F = function () {}; 
 F.prototype = Parent.prototype; 
 Child.prototype = new F(); 
 Child.prototype.constructor = Child; 
 }

这个inherits()函数可以复用

小结

JavaScript的原型继承实现方式就是

  1. 定义新的构造函数,并在内部用call()调用希望“继承”的构造函数,并绑定this;
  2. 借助中间函数F实现原型链继承,最好通过封装的inherits函数完成;
  3. 继续在新的构造函数的原型上定义新方法
  4. 定义prototype里的方法和属性是可以在函数内部,但是如果是直接修改prototype为另一个函数的,最好是在外面,免得忘记执行了(方法和属性因为实例化的时候new已经主动调用了,所以不影响)

小小疑惑

后来我又发现,我们这一步没什么效果

PrimaryStudent.prototype.constructor = PrimaryStudent;

在这里插入图片描述在这里插入图片描述这个时候相当于我们prototype下面就没有构造函数

在这里插入图片描述其实两个真的差不多,难道constructor真的是个摆设吗

我们来测试一下
在这里插入图片描述把整个prototype改空后,运行不了了

只修改了constructor
在这里插入图片描述正常情况
在这里插入图片描述两种好像真的差不多,额所以这里…我个人觉得吧可能constructor只是为了方便我们…,没什么真实作用

在这里插入图片描述
在这里插入图片描述
而且还有一点,就是prototype下定义的变量,函数,如果有新创建对象,一定要注意,所有的值会重新被初始化,因为我们new Student调用了Student,他又会执行一遍赋值的活动,所以…这一点也要注意一下,(因为所有实例都是访问它,所以大家跟着一起变)
但是实例变量就没这个问题了,可以认为是无法修改的,因为你在函数内部你的this绑定的是window(undefined)你怎么也访问不到,所以可以实例变量和方法是无法被改变的,除非你修改实例对象自己的,但还是跟构建函数无关

猜你喜欢

转载自blog.csdn.net/azraelxuemo/article/details/106929178