JS原型、原型链以及继承
一、 什么是原型
创建的每个函数都有一个prototype属性,该属性相当于一个指针,指向一个对象。该对象的用途简单来说就是该构造函数所创建的所有实例共享的方法和属性都被包含在原型内。
二、构造函数、原型对象和实例对象三者关系
先看下面代码:
图中代码首先创建了一个Person函数(构造函数),之后为构造函数的原型对象添加了name属性,接着我们创建一个 person1 实例对象,此时打印person1.name 就能得到name的值 “John”。此时对于三者关系仍然不是很清楚,所以我们可以查看实例对象person1打印的结果:
可以发现,实例对象中有一个 __proto__
属性,该属性展开后可以看到 name 属性,也就是我们刚开始添加给原型对象的属性。所以实例对象中的 __proto__
属性是指向原型对象的。而在 __proto__
内,还可以找到一个constructor属性,该属性展开后可以看到里面是我们创建的Person构造函数。至此,三者之间的关系已经很明确了。三者关系如下:
三、原型链
由上面的例子中可以引出,之所以实例对象能够访问到原型对象上的name属性,是由于JS读取某个对象的某个属性时,都会执行一次搜索,每次搜索都从对象实例本身开始,若在实例对象中找到目标,则停止搜索返回该属性的值,若没有找到,则继续搜索指针指向的原型对象。**而每一个原型对象中也有一个 __proto__
属性,意思是每一个原型对象也有自己的原型对象。 结合上图三者的关系,可以知道原型链之间的关系:
四、继承
继承中有构造函数模式和原型模式,两种方式各有优点和缺点。
-
构造函数模式:
该模式利用call方法改变父构造函数的this指向,同时将传入的参数传给父构造函数,从而实现共享属性。但这种方式定义的方法只能写到子构造函数内,造成函数无法复用的问题。 -
原型模式:
该方式通过让子构造函数的原型对象指向父构造函数实例,从而实现继承父构造函数的属性和方法。但缺点是所有的原型属性都会被所有的实例所共享,所以应该在构造函数中定义属性而不是在原型对象中定义。而这种继承方式中父子构造函数之间的关系如下: