一、原型链
原型链的基本思想就是通过原型继承多个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。
若原型是另一个类型的实例,就意味着这个原型本身有一个内部指针指向另一个原型,相应的另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。
function Person(name) {
this.name = name;
this.age = 18;
this.sayName = function() {
console.log(this.name);
}
}
// 第二步 创建实例
var person = new Person('person')
-
构造函数
Person
存在原型对象Person.prototype
-
构造函数生成实例对象
person
,person
的__proto__
指向构造函数Person
原型对象 -
Person.prototype.__proto__
指向内置对象,因为Person.prototype
是个对象,默认是由Object
函数作为类创建的,而Object.prototype
为内置对象 -
Person.__proto__
指向内置匿名函数anonymous
,因为 Person 是个函数对象,默认由 Function 作为类创建 -
Function.prototype
和Function.__proto__
同时指向内置匿名函数anonymous
,这样原型链的终点就是null
-
一切对象都是继承自
Object
对象,Object
对象直接继承根源对象null
-
一切的函数对象(包括
Object
对象),都是继承自Function
对象 -
Object
对象直接继承自Function
对象 -
Function
对象的__proto__
会指向自己的原型对象,最终还是继承自Object
对象
以字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链。
function SuperType(){
this.property = true
}
SuperType.prototype.getSuperValue = function(){
return this.property
}
function SubType(){
this.subproperty = false
}
//继承SuperType
SubType.prototype = new SuperType()
SubType.prototype = {
getSubValue(){
return this.subproperty()
},
someOtherMethod(){
return false
}
}
let instance = new SubType()
console.log(instance.getSuperValue) //出错
子类的原型在被赋值为SuperType的实例后又被一个字面量对象覆盖了。覆盖后的原型是一个Object对象,而不再是SuperType的实例。因此原型链就断了。
原型链的问题:
- 主要问题出现在原型中包含引用值的时候。原型中包含的引用值会在所有实例间共享,这也是为什么属性通常在构造函数中定义而不是在原型上定义的原因。在使用原型实现继承时,原型实际上变成了另一个类型的实例。这就意味着原先的实例属性摇身一变成原型属性。
- 子类型在实例化时不能给父类型的构造函数传参
二、盗用构造函数
在子类构造函数中调用父类构造函数。可以使用apply()和call()方法以新创建的对象为上下文执行构造函数。
function SuperType(name){
this.colors = ["red","blue","green"]
this.name = name
}
function Subtype(){
//继承SuperType
SuperType.call(this,"tom")
}
let instancel = new Subtype()
instancel.colors.push("black")
console.log (instancel.colors)//["red","blue","green","black"]
console.log (instancel.name)//tom
let instance2 = new Subtype()
console.log(instance2.colors)//['red', 'blue', 'green']
相比于原型链,盗用构造函数的一个优点是可以在子类构造函数中向父类构造函数传参。
盗用构造函数的问题:
- 必须在构造函数中定义方法,因此函数不能重用
- 子类不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式
三、组合继承
综合了 原型链和盗用构造函数,将两者的优点集中了起来。基本思路是:使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。
function SuperType(name){
this.colors = ["red","blue","green"]
this.name = name
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function Subtype(name ,age){
//继承SuperType属性
SuperType.call(this,name)
this.age = age
}
//继承方法
Subtype.prototype = new SuperType()
Subtype.prototype.sayAge = function(){
console.log(this.age)
}
let instancel = new Subtype("Tom",29)
instancel.colors.push("black")
console.log (instancel.colors)//["red","blue","green","black"]
instancel.sayName()//tom
instancel.sayAge()//29
let instance2 = new Subtype("greg",27)
console.log(instance2.colors)//['red', 'blue', 'green']
instance2.sayAge()//27
instance2.sayName()//greg
组合继承弥补了原型链和盗用构造函数 的不足,是JavaScript中使用最多的继承模式。而且组合继承也保留了instanceof操作符和isPrototypeOf()方法识别合成对象的能力。
四、 原型式继承
Object.create()这个方法接受两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象。
let person = {
name:"tom",
friends:["Sheelby","Court","van"]
}
let anotherPerson = Object.create(person)
anotherPerson.name = "gero"
anotherPerson.friends.push("rob")
let yetanotherPerson = Object.create(person,{
name:{
value:"old"
}
})
yetanotherPerson.name = "linda"
yetanotherPerson.friends.push("bob")
console.log(person.friends)//['Sheelby', 'Court', 'van', 'rob', 'bob']
console.log(yetanotherPerson.name)//old
原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。这种继承方式的缺点也很明显,因为Object.create
方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能
五、寄生式继承
与原型式继承最接近的一种继承方式是寄生式继承。寄生式继承类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
function createAnother(original){
let clone = Object(original) //通过调用一个函数创建一个新对象
clone.sayHi = function(){ //以某种方式增强这个对象
console.log("hi")
}
return clone //返回这个对象
}
let person = {
name:"tom",
friends:["Sheelby","Court","van"]
}
let anotherPerson = createAnother(person)
anotherPerson.sayHi()//hi
在代码段中,createAnother()函数接收一个参数,是新对象的基准对象。这个对象original会被传给object()函数,然后将返回的新对象赋值给clone。接着clone。接着给clone对象添加一个新方法sayHi()。最后返回这个对象。
六、寄生式组合继承
组合继承存在着效率问题,就是父类构造函数始终会被调用两次:一次是在创建子类原型时调用,另一次是在子类构造函数中调用。本质上,子类原型最终是要包含超类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就好。
寄生式组合继承通过盗用构造函数继承属性,但使用混合原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本(就是使用寄生式继承来继承父类原型,然后返回新对象赋值给子类原型)
function inheeritPrototype(Subtype,superType){
let prototype = Object(superType.prototype) //创建对象
prototype.constructor = Subtype //增强对象
Subtype.prototype = prototype //赋值对象
}
function SuperType(name){
this.colors = ["red","blue","green"]
this.name = name
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function Subtype(name ,age){
//继承SuperType属性
SuperType.call(this,name)
this.age = age
}
//继承方法
inheeritPrototype(Subtype,SuperType)
Subtype.prototype.sayAge = function(){
console.log(this.age)
}
这里只调用了一次SuperType构造函数,避免了Subtype.prototype上不必要也用不到的属性,可以保持原型链不变,instanceof操作符正常有效。