原型和原型对象,原型链
原型与原型对象
js继承
class Teacher{
constructor(name,subject){
this.name = name
this.subject = subject
}
speak(){
console.log('我是'+this.name);
}
teach(){
console.log('我教'+this.subject);
}
}
class Student{
constructor(name,score){
this.name = name
this.score = score
}
speak(){
console.log('我是'+this.name);
}
instroduce(){
console.log('我考了'+ this.score +'分');
}
}
以上代码使用class
定义了两个类Teacher
和Student
,他们都有一个共同的属性name
以及一个共同的方法speak()
,那么我们就可以定义一个父类Person
来保存他们共同的属性和方法,于是,我们可以使用extends
来让Teacher
类和Student
类继承Person
类,这样,我们就可以将代码改为:
class Person{
constructor(name){
this.name = name
}
speak(){
console.log('我是'+this.name);
}
}
class Teacher extends Person{
constructor(name,subject){
super(name)
this.subject = subject
}
teach(){
console.log('我教'+this.subject);
}
}
class Student extends Person{
constructor(name,score){
super(name)
this.score = score
}
instroduce(){
console.log('我考了'+ this.score +'分');
}
}
原型
如果我们实例化一个Student
对象Tom
,Tom = new Student('Tom',99)
,在浏览器的控制台中输入Tom可以看到如下结果:
我们展开可以看到,这是Tom对象的属性name
,和score
,以及一个不认识的[[Prototype]]
,它的值是Student
类的父类Person
。这个整体就是Tom
对象的原型,属性name
和score
被称为显式原型,而这个[[Prototype]]
就被称为隐式原型,在代码和旧版Chrome浏览器中,它应该被写作__proto__
,新版的Chrome浏览器改成了现在的样子。
我们再将这个原型展开之后可以看到,里面是Tom
对象所用到的构造函数constructor
,以及Student
类中包含的方法introduce
,以及又一个原型[[Prototype]]
。这个原型后面的值为Object
,也就是说Person
类的原型是Object
。
通过hasOwnProperty()
方法,我们可以判断对象自身是否拥有某一属性,在下图中可以看到,Tom对象的name
属性是自身的,而introduce()
方法不是,但是我们可以调用这个方法,因为它在Tom对象的原型上
原型对象
如果我们在浏览器中输出Tom.__proto__
(注意左右两边都是两个下划线),可以看到如下结果
和之前看到的[[Prototype]]
中的内容一样
我们知道,Tom
对象是通过Student
类创建的,而Student
类有一个属性叫做protoType
,我们在浏览器中输出可以看到
这两个输出的内容一模一样,那么他们是相等的吗?可以看到,确实是相等的,他们指向的是同一个东西,这个东西被称作原型对象。
原型链
之前展开Tom
对象的原型后,我们在里面还发现了一个新的原型,再将它展开,可以看到,里面是Student
类的父类Person
的构造函数constructor
以及方法speak()
,这个原型里面还有一个原型,因为Person
类没有继承别的类,所以它的原型将会是Object,也就是所有原型的终点。
再继续展开,可以看到很多东西,但是这里面不再有[[Prototype]]
以上从Tom
对象,到Student
类,再到Person
类,再到Object
的所有原型串起来,就形成了原型链。而javascript的继承就是通过访问原型链上的属性和方法来实现的。
当我们要访问一个对象的属性或者方法时,会先在自身上寻找,如果找不到,会去自己的原型上找,还找不到就去原型的原型上去找,以此类推,最终找到这个属性或者方法并进行访问和调用,这个过程就形成了原型链。
例如我们使用Tom.speak()
方法时,会现在Tom
对象上找这个方法,但是没有找到,于是我们会去Tom
对象的原型上,也就是Student
类的原型对象上找,但是也没找到,于是我们继续在Student
类的原型对象的原型上找,也就是Person
类的原型对象,找到了,于是访问调用并输出。
可以通过下图来理解原型链
图中红色箭头的路径就是原型链,原型链最终会指向一个值为null
的原型。
如何利用原型链去判断数据类型
我们一般会使用typeof
去判断一个东西的数据类型,但是对于数组和对象,这个方法就不好用了。
let a = []
let b = {}
console.log(typeof a,typeof b);
/*运行结果
object object
*/
可以看到输出的结果都是object
,因为数组Array
其实也是一个对象,它的原型链上的最后一个类也是Object
,那么我们要怎么区别数组和对象呢。
虽然原型链的终点都是Object
,但是不同的对象在原型链的中间部分是不一样的,而数组对象Array
就有这样一个特别的原型对象Array
。于是,我们只要找到了原型对象Array
,不就证明这是一个数组吗。
我们可以使用instanceof
关键字来判断对象的原型链上是否有这一个原型对象。
let a = []
let b = {}
console.log(a instanceof Array,b instanceof Array);
/*运行结果
true false
*/
可以从输出结果中很清楚的辨别出数组和对象
js中new一个对象的过程
首先我们会想到调用构造函数,但是并不是这么简单,这只是其中一步。
以Tom
对象为例,第一步是创建一个新对象Tom
,然后它会被执行[[prototype]]
链接,也就是链接到原型链上,对象被创建时就会带有一个__proto__
属性,通过Tom.__proto__ = Student.protoType
即可实现链接。然后新对象Tom
会和函数调用的this
绑定起来,让this
关键字指向正确的对象,第四步才是调用构造函数,给对象赋上属性。最后一步,如果构造函数没有返回值,那么将自动返回this
对于最后一步,可以通过以下这个例子理解:
function A(name){this.name = name}
function B(name){this.name = name}
a = new A('a')
b = B('b')
运行结果如下
可以看到,new关键字会自动给函数返回this
,如果没有使用new来创建对象,那么必须要在构造函数中进行return this
,才能正确地创建对象。